時計を壊せ

駆け出してからそこそこ経ったWebプログラマーの雑記

素人が動画配信ライブのPAをやってみた振り返り

※この記事はソフトウェアエンジニアではなくサウンドエンジニアリングの話です

※めっちゃ長いです

背景

TECH x ROCK Festival 25.01 スタジオ配信ライブ! - connpassというイベントをやることになり、会場となるスタジオからインターネット上に配信を行おうとなりました。 素朴に空間を普通のマイクで録るだけとかいろいろ選択肢はあったんですが、スタジオにYAMAHA QL-5も常設されていることだし何より個人的にPAにチャレンジしてみたかったので、 ちょっとだけ本格的なPAをやらせてもらうことにしました。

個人的には、自分のバンドや自分の演奏のセルフレコーディングは何度もやったことがありますが、PAはほとんどやったことがありません。 それも、ボーカルだけマイクで拾うだとか、せいぜいそこに加えてアコギを追加でマイクを立てるくらいの比較的簡単なPAでした。

今回は配信PAということでこれまでより難易度が高いことにチャレンジできそうだし、ミキサーの操作やサウンドエンジニアリングの基礎には一応多少なりとも心得があるので良い機会かなということで挑戦させてもらうことにしました。

その個人的な振り返りも兼ねて、同じようなことをやってみたい同じPA素人の人に参考になる情報が提供できればいいなと思ったので記録に残します。

PAってなんのこと?

※このブログはソフトウェアエンジニアの読者が多いので一応PAそのものについても解説します。ただし、あくまでも素人解説なので参考程度にお願いします。より正確なことを知りたい方はご自身で改めてお調べください。

PA(Public Address)とは和訳すると大衆伝達とか言われたりしますが、ざっくりいえば音声を届けたいひとに適切な音量・良い音質で届けること、またはそのためのシステムのことを指します。

身近なところだと、プレゼンテーションをするときにマイクを通してスピーカーからも声が出るようになると話が聴きとりやすくなりますよね。 それを、良い感じの音量にしたりハウリングしたりしないように調整したりしますよね。これも簡単ですがPAです。

PAを実現するために必要なものは大きくわけて3つあります。*1

  1. 音声の入力元となるもの(マイクなど)
  2. 入力された音声の音量バランスなどを調整し、出力先にそれらをミックスして出力するもの(ミキサー)
  3. 音声の出力先となるもの(スピーカー、オーディオインターフェースなど)

先ほどのプレゼンテーションの例だと、マイクを使ってプレゼンターや司会者の声を拾い(1)、ミキサーで各マイクの音量バランスを調整し(2)、スピーカーから出力する(3)ことでPAを行っています。

PAをするために必要なものを総称してPAシステム、PAシステムを操作してPAを行う人をPAオペレーターと呼びます。 単にPAと言ったときにはPAそのものを指すこともあれば、PAシステムやPAオペレーターを指すこともあります。文脈に応じて使い分けられます。

配信ライブのPAってなにをするの?

一般的に配信ライブのPAは以下の3つが目的になります。

  1. ライブ配信を観る人にライブの音声を適切な音量・良い音質で届けること
  2. 観客となる人にライブの音声を適切な音量・良い音質で届けること(現地有観客の場合のみ)
  3. 演奏者に演奏しやすい音量・音質で必要な音声を届けること(モニタースピーカーなどがある場合のみ)

実はこれらはすべて、それぞれ必要になる音声や音量バランスは異なります。つまり、それぞれに合わせて別々のバランスで音をミックスする必要があります。

1つめはある意味ではとてもシンプルです。演奏を演奏として聴きやすい音量バランス、音質にすることが目的ですので、出力そのもので良いバランスになるように調整していくことになります。

2つめは少し複雑で、観客には実際に楽器から鳴っている音も直接聞こえてきます。つまり、これに補完する形で足りない音を増やすという考え方が必要になります。*2 実際に聴こえてくる音、理想の音量バランスに足りない音をPAからも出してバランスを調整していくことになります。

3つめも同様ですが、これは一般的に各演奏者にごとに調整します。各演奏者ごとに立ち位置が異なることから他の楽器の聴こえ方がそれぞれで異なります。それぞれに合わせてバランスを調整するために、各演奏者の要望に応じてバランスを調整していくことになります。

このように、PAを整える上ではそれぞれに違ったバランスにミックスした音を出力したくなることが珍しくありません。

そのため、ミキサーにはメインとする出力のバランスを調整する機能はもちろんのこと、その他の出力のバランスを調整するための機能も備わっています。

今回使用したYAMAHA QL-5の場合はメインとなる出力のほかにMIXと呼ばれる出力が定義されています。(MATRIXという機能もありますが話の大筋から外れるので割愛します。)

たとえば、このMIXはこのモニタースピーカー、このMIXはオーディオインターフェイスに送る左側の音声、このMIXは右側といった具合で設定します。その上でそれぞれのMIXに適切なバランスになるように音声を送ります。これをセンド(Send)と呼びます。

つまり、それぞれの目的に沿うようにメインの出力とMIXの出力をそれぞれ定義し設定し、実際の演奏を収録する入力を定義し設定して、それぞれの音量バランスを適切に調節することで目的を達成します。

YAMAHA QL-5

一般的な施設に音響機器として既設されているのはアナログミキサーであることがほとんどですが、今回はデジタルミキサーであるYAMAHA QL-5が既設されているのでこれを利用します。

デジタルミキサーとは、ミキサーの機能をデジタル技術を用いて実現するミキサーです。

電気的には、アナログミキサーでは入力から出力までアナログの電子回路を通して動作しますが、デジタルミキサーでは入力信号をA/D変換してデジタルデータ化してミキサーの処理を行いD/A変換して出力するという違いがあります。

この違いによって、デジタルミキサーはアナログミキサーより柔軟な操作が可能です。デジタルミキサーの利点は何よりもその柔軟さにあります。

たとえば、フェーダーやノブに別の機能をアサインすることでセンドやグラフィックイコライザーなどの様々な設定を直感的な操作感で設定することができたり、シーン(Scene)として設定を保存・再設定することができるほか、リンクしたチャンネルに対してフェーダーの動きを連動させたりなど、デジタル化した利点を活かした操作・設定が可能です。したがって、デジタルミキサーはアナログミキサーで同じことをするより簡単に様々な設定・操作が実現できます。

その反面として、デジタルミキサーはその操作方法がアナログミキサーと比べて直感的ではありません。

デジタルミキサーを扱う場合、そのミキサーが各機能をどのような概念として整理しているのかを理解し、それがノブやフェーダー等のインターフェイスとどのように紐付けられるのかを理解する必要があります。

そのため、そのミキサーを正しく理解して使わないといけないという意味ではデジタルミキサーを使う事は非常に難しいといえますが、反面それを正しく理解して適切に利用することができれば非常に便利に利用することができます。

また、デジタルミキサーはアナログミキサーの操作性にある程度近づけたインターフェースを持っているため、アナログミキサーの操作方法を理解したうえで学習したほうが理解が早いかと思われます。そういった側面もあるので、総合的に考えると上級者向けのミキサーといえるでしょう。

今回はデジタルミキサーであるYAMAHA QL-5が設置されており利用できることが予め分かっていたのと、そのためのセルフトレーニングの動画など学習コンテンツも充実していたことから、これをそのまま利用する方向で準備を進めました。

事前準備

QL-5を利用するにあたって、そのまま当日を迎えると大変なことになる未来が目に見えていたのであらかじめ準備をしておきました。

他の選択肢

RolandのSTUDIO-CAPTUREというオーディオインターフェースを持っているので、これのミキサー機能を利用してPAをやることも考えました。

www.roland.com

これを使うメリットは同一ネットワークのiPadへのネットワーク内ディスプレイミラーリング機能など、何らかの手段でPCのリモート操作を行うことができれば別室からPA操作が可能である点です。 自分の手持ち機材なので比較的設定や操作に慣れていることや配信に載せる音を聴きやすい環境でコントロールできる点でメリットがありましたが、持ち物が増えることはもちろん今回はQL-5を使ったやり方にチャレンジしてみたい気持ちからこれの利用は見送りました。

セルフトレーニング(1周目)

まずはヤマハ | QL Seriesにあるセルフトレーニング動画と説明書を参照して使い方を学習しました。 セルフトレーニング教材はいちから全てをセットアップするような内容も多かったので最適限必要そうなものだけをピックアップして学習しました。

ざっくりこのあたりのトレーニング動画を観たと思います。

  • 1.1. QLコンソール概要
  • 3.1. キュー/モニター/トークバック設定
  • 3.2. Selected Channel画面とTouch and Turn
  • 3.3. シーン000 - インプットパッチの初期化
  • 3.4. インプットチャンネルの基礎
  • 3.7. インプットチャンネルからMIXバスに送る
  • 3.8. 出力バスの基礎

これで粗方の簡単な操作の理解とイメージはこれである程度は身につきました。

重要なポイントを自分なりに要約するとこんな感じでしょうか。

  • インプットチャンネルは物理的な入力ポート*3とイコールではない
    • すべてのチャンネルは仮想的な概念になっている
    • インプットチャンネルと物理的な入力ポートの紐づけを設定することで入力ポートを利用できる
      • アナログミキサーとは異なりフェーダーの位置関係を優先してチャンネルを設定可能
      • どの楽器用の入力としてどの入力ポートを利用するかは配線の都合を優先して決定できる
  • 同様に、出力ポート*4はアウトプットチャンネル(MIX/MATRIX/ST/MONO)に紐づけを設定することができる
  • タッチパネルとTouch and Turnノブを利用してほとんどのパラメータを操作することができる
    • 各チャンネルを選択してから操作する
    • アナログゲインやEQなどは専用のノブがある
    • ダブルタップすることでポップアップが出てより詳細な設定ができるパラメータもある

現地下見(1回目)

そして、一度現地へ下見に行きました。 オンライン配信自体のリハーサルも兼ねて行ったので、楽器を鳴らす人なども含めて5~6人ほど、3時間程度で行いました。

このときの設定の方針はざっくりこんな感じです:

  • 基本的にスタジオのプリセット設定から乖離しすぎないように配慮して設定する(設定の手間を最小化しようとした)
    • 入力ポート1~16はマルチケーブルボックスにつながっているので、入力ポート1~16をインプットチャンネル1~16にシンプルにわかりやすくアサインして利用する(スタジオのプリセット設定と同じ)
    • 出力ポート1~5は足元のコロガシに紐づいているのでそれと紐づけたMIX1~MIX5をコロガシ用にする(スタジオのプリセット設定と同じ)
  • 出力ポート7/8はMIX7/MIX8と紐づけてステレオで配信用のオーディオインターフェースに信号を送る

また、PAの方針としてはこんな感じで考えました。

  • シンプルにするため、基本的には空間全体を録るステレオのコンデンサーマイクの音をベースに配信の音を作る
    • 空気感が強く出るのでライブ感が出せそう
    • 一方で低音感やドラムのアタック感が物足りなくなりそう
      • かといってローを出しすぎるとモコモコしがちなので60Hz~120Hzあたりを目安にHPFをかける
  • ベースはDI、ドラムはバスドラムとスネア(トップ)にマイクを立てて音を録る
    • 低音感やアタック感を補完する
  • ほか、ボーカルと管楽器のためにマイクを立てる
    • 中音を作るために必要
    • 配信にも適度な塩梅で混ぜる
  • キーボードはDIで入力する
    • 今回はキーボードの人がいないので確認見送り

いろいろと大変な時期で正直すこし準備不足で挑む羽目になってしまったのですが、とりあえず雑に音を送るくらいのことはどうにかできるようになりました。

一方で音圧がちょっと物足りなく感じたり、QL-5のその柔軟さと奥深さからより良いセッティングが出来るはずだとも思わされて、 もう少し音を詰められるようにマイクのセッティングを見直したりもっとQL-5を使いこなせるようになりたいなと思いました。

セルフトレーニング(2周目)

そこで、正月の空いた時間を使ってセルフトレーニングをもう一度、今度はあんまり関係なさそうな動画も含めて観てみることにしました。

すると、QL EditorとQL StageMixという2つの発見がありました。(あとから見返すとなんで気づかなかったんだと思うわけですが……。)

QL EditorとはPCで動作するアプリケーションで、QL-5などYAMAHA QLシリーズ向けの設定を作成・編集しUSBメモリに保存してQLシリーズに読み込ませることができます。 つまり、QL Editorを使えば自宅でミキサーのセッティングをある程度済ませた状態でスタジオ入りすることができるのです。

すると、前回の方針であったスタジオでのミキサーセットアップの時間を気にする必要がほぼなくなります。 これを活用すれば設定の手間をあまり気にせずチャンネルのアサインをより最適化することができそうです。

前回もこれをやっておけば……そしてUSBメモリを持参していれば……とちょっと後悔しました。 さっそくQL Editorを使って設定をより具体的に詰めていきました。

QL StageMixはiPadなどのタブレットで動作するミキサーのリモートインターフェースです。要はiPadからWi-Fiとイントラネットワークを通してQLシリーズのミキサーを操作できるものです。

今回のスタジオではミキサー卓と演奏者が同じ空間にいるので配信のミックスを行うのにちょっと苦労しそうなことが課題としてすでに見えていたので、これが使えると非常に便利そうだということが伺えました。

ほか、バーチャルラックの利用方法も理解したので、よりセッティングが詰められそうだということがわかりました。

そんなわけでもう一度本番の前に実機を触りたいと思い、2度目の現地下見をすることにしました。

現地下見(2回目)

スタジオノアを含むほとんどのリハーサルスタジオには個人練習という料金システムがあり、 前日21時からしか予約できない代わりに1人または2人での利用に限り安い料金でスタジオを利用することができます。

今回はこれを利用して1人でスタジオに入ってQL Editorで作った設定ファイルをQL-5で読み込ませるリハーサルをしてきました。

このときの目的は以下の3点です。

  • QL Editorで作成した設定が期待どおりであることを確認する
    • 設定を読み込むことができること
    • 設定した項目が期待通りに利用できることを書くする
  • QL StageMixの利用可否を確認する
    • ネットワークが必要なのでミキサーがネットワークに接続されているかどうかを確認する
    • 接続されている場合、IPアドレスを確認しWi-Fi経由で実際に接続が実際にできるかどうかを確認する
  • 前回の下見で見落としていたことがないか確認する
    • ミキサー卓の移動可否、その他のオブジェクトの移動可否など

あらかじめUSBメモリ*5にQL Editorで作った設定ファイルを入れておきスタジオに入ります。

スタジオの初期セッティングを壊さないよう、USBファイルにバックアップを取った後に自分の設定を反映します。バックアップも事前作成した設定も無事に反映できたことが確認できました。

次は入力ポートや出力ポートなどの設定が実際の機材の入力ポートと合っているかを確認します。特に入力ポートは数が多く当日の修正は困難なので念入りに確認をしていきます。 今回は無料でレンタルできるマイクを各入力ポートに接続して確認しました。

アウトプットの確認は主にメインスピーカーと足元のいわゆるコロガシと呼ばれるモニタースピーカーです。同様にそれぞれの出力ポートが期待通りのミックスにアサインされていることを確認しました。

設定が期待通りに作成できている部分、また修正が必要な部分を確認してまとめます。今回は入力ポート1~16を超える部分、入力ポート17以降を直接ミキサー卓に接続できるか確認しましたが、一部壁コンセントなどを経由して入力できるような構成だったため、純粋な空きポートは限定されていました。(前回の下見では壁コンセントの入力ポートの存在は見落としていました。) 同時に、壁コンセントへの配線の都合からミキサー卓の位置を大きく動かすことは困難であることがわかりました。

そこで、一部インプットチャンネルを壁コンセント経由で利用するような設定に変更することにしました。

QL StageMixの利用は残念ながらできませんでした。ミキサーがネットワークに接続されていなかったためです。 ミキサーを勝手にネットワークに接続して万が一なんらかのセキュリティ上の問題が引き起こされても責任は取れないため、今回は利用を諦めることにしました。

これを踏まえてQL Editorで設定を再度調整します。

他の諸々も踏まえて修正した方針としてはこんな感じです。

  • 基本的には前回と同様
    • 空間全体を録るステレオのコンデンサーマイクの音をベースに配信の音を作る
      • 空間でローを出しすぎるとモコモコしがちなので60Hz~120Hzあたりを目安にHPFをかける
    • ボーカルと管楽器のためにマイクを立てる
    • ベースはDI、ドラムはバスドラムとスネアにマイクを立てて音を録る
  • フロアタムにマイクを立てて音を録る
    • 前回、フロアタムの低音感が物足りずスカスカに感じたので補完する(他のタムはそこまででもなかったので妥協する)
  • ギターアンプにマイクを立てて音を録る
    • 低音ズシズシする感じのギターを入れたいバンドがありそうだったので追加
  • キーボードはDIでステレオ入力する
    • 特に要望があったわけではないがチャンネルも余っているしステレオにするのがあまり手間でもなかったからせっかくなので
  • メインフェーダーをメインの出力と考えたほうが直感的なのでSTを配信ミックス用に利用
    • 前回はセンドしていたのでバランスを詰めるときにSEND ON FEDERモードにしないと見づらかった
    • もともとSTにアサインされていたメインスピーカーはMIXにアサインして、コロガシのモニタースピーカーと並列に扱えるようにした

この方針のもとある程度の設定をQL Editorで作り込んで当日に挑みました。

この際に、いくつかのエフェクトもある程度仕込んでいきました。

  • 各チャンネルのHPFをある程度あたりを付けて余裕持たせて仕込む
  • バスドラム、スネアドラムにはゲートを、フロアタムにはエキスパンダーを仕込む
  • バスドラム、スネア、ベース、そしてボーカルマイクにはプレミアムラックのU76(1176系のエミュレータ)をインサート
    • ほか、コンプを入れたくなりそうなところにはとりあえず入れられるだけU76を入れた(1176系は個人的には比較的慣れててまだ感覚がわかる/コンプの種類を選べてないあたり素人っぽさが出ている)

当日

当日はミキサーにあまり詳しくない人にも手伝ってもらえる体制をつくりたいので、配線の表とガイド図を印刷して持っていきました。

部屋全体の間取りと大まかな機材の配置、マイク等の設置位置と配線先の入力ポートの番号を記載した配線のガイド図。
配線のガイド図

どの入力ポートに何を接続するのか、どのような用途なのかをまとめた表。
Spreadsheetにまとめた配線表

これを参照して手伝ってもらい数人がかりでマイクやDIなどを配置・配線しました。

ミキサーの既存設定をバックアップした後、事前にQL Editorで作成した設定を読み込ませます。 無事に読み込んだことを確認したら各入力ポートが期待通りのチャンネルにアサインされて期待どおりの入力をコントロールできることを確認しました。

次に、適切にマイクで音を拾うことが一番大切なのでマイクの位置を調整していきます。

まず、全バンドで共通のセッティングで利用する空間マイクを念入りに調整します。

次に、ギターアンプのマイク位置の調整です。ギターアンプは音の出をチェックして一番鳴りが良いスピーカーのスピーカーコーンの端から少し内側の位*6を狙って調整、ギターアンプの入れ替えがあっても後ですぐ再セッティングできるよう養生テープで目張りします。

ほか、ドラムもマイクスタンドが邪魔にならないよう置き方を改善、どの角度からマイクを出してマイキングをするか大まかな調整をします。

そして、各バンドのリハーサルを通じて各バンドの演奏に合わせて再調整していきます。 配信中も配信を通じて聴ける音とモニタヘッドホンを爆音のなかでなんとか聴きながら、自然と耳に入ってくる中音も聴いて、不自然に大きく変化しないよう注意しつつ可能な限りの調整をしていきました。

各バンドそれぞれについて何があったかは詳細に書くときりがないので端折ります。

実際の配信に乗った音や映像はアーカイブから視聴することができます。

Latest - jyoshise (@jyoshise) - Twitcast

そして、なんとか当日を乗り切ることができた。という感じでした。

振り返り

KPT形式で振り返っていきます。

Keep

続けたいこと、よかったこと。

楽しめた

趣味でやるのに楽しめてなかったらおしまいなので楽しめたのが個人的にはとても良かったです。ある種の知的好奇心を満たせました。

そして何よりPAの難しさに直に触れることができたのが本当に良かったです。本職の方々へのリスペクトが高まりました。もちろんたくさん反省点もあるし悔しい気持ちはたくさんありますが、経験そのものへの満足感は高かったです。

デジタルミキサーの利用

予習をしたおかげもあって、ある程度はうまく使いこなすことができました。 結果として、手荷物をかなり減らすことができて、特に今回は自分も演奏するのでベースを担いで持って行く必要があったため荷物を減らすことができてとても良かったです。

今回の持ち物は細かい私物を除いてこれだけで済みました。(他の必要なマイク類などはすべてレンタルで賄いました。)

  • 自分の演奏に必要なもの
    • ベース&周辺機材
    • 自分のコーラス用マイク (audio-technica AE6100)
  • 配信PAに必要なもの
    • バスドラム用のマイク(AUDIX D-6)
    • 空間用マイク(RODE NT5 pair)
    • USBメモリ
    • モニターヘッドホン(SONY MDR-CD900ST)
    • パッチケーブル(ベースアンプのDI用)

カンファレンスノベルティのトートバック2袋に余裕を持って収まる量だったので助かりました。

QL Editorでの事前の設定作成

当日の設定時間を大幅に短縮できたし、各チャンネルに適切な命名とアイコンの付与が行えました。

当日にミキサー上で同じ設定をしようと思ったら1~2時間はかかりっきりになるしかないと思います。 ある程度の設定を作って当日調整するだけで良いのでとても楽でした。

また、ミキサーとUIは違いますが同じ概念を扱っているので結果として設定を作ることが操作のイメージトレーニングにも役立ったという思わぬ収穫もありました。

配線のガイド図などの作成

ミキサーにあまり詳しくない人にも手伝ってもらえるくらいの資料に落とし込むことで、当日のマイクの配置やケーブルの配線を分担して行うことができました。

今回はPA担当を自分1人で回したため、ここが分担できたことでPA知識がない人に任せられない、自分にしかできない作業に集中することができたのが大きかったです。

中音の評判は悪くなかった

演奏しやすいと言ってくれた方もいて、中の音のバランスはそこまで悪くないものにできたの かなと思います。

Problem

問題になったこと、良くなかったこと。

音割れ対策の甘さ

最大レベルを合わせることなく配信に入ってしまい、また思ったよりリハーサルも慌ただしくなって調整のタイミングを逸してしまったことで音割れ対策が甘くなってしまいました。 結果的に、いくつかのバンドで音割れのある状態で配信してしまうことになってしまいました。

もともと頭の中では考えていた、オシレーターを活用したレベル合わせも当日は頭から飛んでしまっていました。

音作りの方針がブレた

もともとは空間マイクに合わせて物足りないものを足すという方向性だったはずが、だんだんモワッと感を解消したい気持ちが強くなってきてしまい空間の音の割合を下げて各マイクの音量を持ち上げるなどした結果としてだんだんもともと作ったセッティングと乖離していってしまいました。音割れにもつながっていた可能性もあります。

初志貫徹できれば多少音質が悪くとももっと安定した音を届けられたはずなので、もっと妥協すべきでした。

自省して、経験不足による不安を解消しようとしてしまった心理から判断の優先順位の誤りがあったための判断ミスだったと思います。

モニター環境の難しさ

ヘッドホンでモニターすることは出来たのでそこそこの音量のバンドだとモニターは問題なくできたのですが、出音が大きなバンドだとかなり音を大きくしないと聴こえず難しい場面がありました。

中音を作るだけならこれでもあまり困らないのですが、配信の音を作ることを考えたときにどうにも正常にモニター出来ないので、こうなってくるとほとんど勘で調整するしかありません。

当初の方針のまま、空間の音を中心に味付け程度に個別の音を足す形であれば音量差の影響は比較的対処しやすいのでそれでもなんとか出来た余地はあったと思うのですが、方針がブレてしまったのもありより難しさに拍車が掛かってしまいました。

PA担当しながら演奏もした

ただでさえ不慣れなPAが演者も兼ねたことでますます中途半端になってしまいました。中途半端なことをするのは良く無い。

一方でどちらも楽しめたのは良かったので、迷惑かけないくらいのスキルになれたら両方やっても良い気はする。

各バンドのリハーサルで配信の音をチェックするタイミングを作れなかった

リハーサルの際にアナログゲインの調整や演奏者のモニター返しの調整、中音の調整にかかりっきりになってしまい、配信の音の調整まで十分にできる余裕が作れませんでした。 また、時間に対する焦りからかそのまま配信に入ってしまいました。結果として上記の2つの問題の原因になっていた可能性もあります。

計画時にこれに気づいて時間の余裕をもっと作れていたら、当日にも時間が押してでもチェックができていたら良かったなと思います。

リハーサルのやり方が冗長だった

リハーサルのやり方が冗長だったなと感じるポイントがいくつかありました。

ドラムのインプットゲインの調整を全バンドで各楽器ごとに行っていたのですが、演奏に入るとまた大きくゲインが変わることがほとんどでした。 ドラム全体の演奏で調整すれば十分だったかもしれません。

配信の音バランスからポストフェーダーで音を作ったのは良くなかった

配信の音割れを気にして音量を落とすとモニターの音量も落ちてしまい中音のバランスが崩れるという問題につながりました。 どうせ別々にバランスを調整するのであれば最初からプリフェーダーセンドで設定しておくべきでした。

結果的に、リハーサルのスムーズさを落とす要因につながりました。

PA用のマイクの用意をしていなかった

リハーサルではPAから演者にお願いをする場面があるのですが、その際にマイクがないと演奏中に割り込んでメッセージを伝えたりといったことができません。 最初の2バンドほどでは用意ができておらず、後からマイクを頑張って足してなんとかしましたがスタジオとはいえ甘く見ずに最初から想定して準備しておければよかったです。

PAを1人で回してしまった

もっと早くに他の人を巻き込んでデジタルミキサーの勉強会をしたり、マイキングの勉強会をしたりして一緒にPAを回せる仲間を増やせたら良かった。(とは思うが、正直いろいろと忙しくてそんなことまでを考えられる余裕は正月休みくらいしかなかった。。)

転換時にマイクセッティングの変更などをするのに手間取ったりしていたが、アシスタントをしてもらったり中音の調整といったデジタルミキサーの一部操作や音割れくらいの簡単な配信音チェックを任せられる人がいるだけでもクオリティは大きく違ったはずなんじゃないかなとは思う。

Try

もし次があるならこうしたら良いのではないか

音割れ対策をちゃんとする

PAと配信用オーディオインターフェイス間はオシレーターを使って、ミキサー側のフェーダー0dBとオーディオインターフェイス側のゲインのマッチングをすれば防げるはずなので次はなんとかしたいです。(これは本番前には思い付いてはいたので咄嗟に思い出せず実行できなくて悔しい……!!) これは、ソフトウェアエンジニアの方はユニットテストをしておくと安心感が違うって思ってもらえるとだいたいあってます。

PAの中での音割れは本番中はマスターにリミッターを挟むことで本番で突然音割れするケースは防げるはずなのでこれも仕込みたいです。

方針を変えるならリハのタイミングで必ず音のバランスを完璧にする

モニター環境が不完全な中で音のバランスを完璧にするのは不可能なので、そういうときは諦めて初志貫徹する。

整えられるなら時間との勝負なので算段が付きそうか、元の方針のままで行く場合とのリスクを比較して判断する。

モニター環境をなんとかする

別室からモニターしつつミキサーを操作して配信ミックスのバランスを調整できると理想。

QL StageMixやXLRのラインをワイヤレスで飛ばせるやつを活用すれば今回と同様の環境下でも実現できるはずだと思います。

PA周りを複数人で回すためのロールを定義して勉強会をする

PAは卓のコントロール以外にも色々やるので、アウトソースできるところはとことんアウトソースしてチーム戦にするのが理想的な気がしました。(結果として一般的なライブハウスのPAの人員構成に近づきそう。)

ライブハウス並まではいけなくとも、趣味として興味のある人がある程度集まれれば、各々が勉強や実験をしてノウハウ共有をし、最終的に役割分担をすることでマシにはできそうな気はします。

リハは多めに取る

普通のライブハウスのPAは20分かそこらで調整してしまいますが、そもそものPAオペレーションだけではなく環境と機材にも不慣れなうえに、演者への返しと中音と配信の音とを優先順位の違いこそあれど全部いい感じにする必要があります。

それを30分で終わるのは見積もりが甘すぎたので、次やるならせめて40分は最低でも見込んでベストエフォートで早く終わるが良さそうです。

あと、そもそもPAも含めたサウンドチェックと調整のために演奏のリハをしているということを理解してもらう必要性も少し感じました。(特にライブ慣れしていない人も多いこういう場ではちゃんと丁寧に説明するべきでした。)

自分たちはこれで行けそうなのでリハ終わります〜ってなりそうになってもうちょっとやってもらった場面があったり、PAもゲイン調整してるのに演者もラインの出力を調整していて永久にマッチングしなかったり、そういうのを減らすためにちゃんとどんなときに何を求めていて何をやらないでほしいのかちゃんとコミュニケーションしないといけなかったなと反省しました。

PAは演者を兼ねない

それはそう。

iPhoneのマイクから配信する

とことん省エネでやるならこれが一番クオリティ高かったんじゃないか説は正直あります。iPhoneはすごい。

おわりに

素人ながらPAについて少しは理解を深めることができたのではないかなと思います。貴重な機会をもらえてとても嬉しかったです。

たぶんいないとは思いつつ、もし、同じようなスキルの同じような集団が同じようなことをしようとしたときに参考になれば嬉しいです。

*1:厳密には音声の出力先がスピーカーの場合はその音声信号をもとにスピーカーを駆動させるためのパワーアンプも必要になるので、4つに分けて説明しているものが多いです。が、説明をシンプルにするために省略します。

*2:厳密には会場の広さ等々の条件に寄ります。

*3:簡単のために、このブログでは一貫してOMNI INポートのことを指します。

*4:簡単のために、このブログでは一貫してOMNI OUTポートのことを指します。

*5:ExFATなどはサポートしていないのでFAT32でフォーマットしておく必要があり、注意が必要です。

*6:経験上、センター寄りに配置するより少し太めの音で録れるので空間の音と混ぜるにはこのへんが扱いやすそうと思って狙いました。

Google Cloud Firestore datastore modeをいい感じに操作するやつを作った

Google Cloudにおいて、古くはApp Engine Datastore、その後にCloud Datastore、現在はCloud Firestore datastore modeという形で存在するものがある。 俗に単にDatastoreと呼ばれることが多いと思うので、このエントリでは単にDatastoreと呼ぶことにする。

知らない方向けに簡単に説明すると、このDatastoreはスキーマレスでキーバリューストアライクな構造を持つGoogle Cloudのマネージドなドキュメントデータベースになっている。いわゆるNoSQLデータベースの一種。

キーバリューストアとはいうが、インデックスを貼ることである程度柔軟な検索をすることが可能で、キーを分散させる*1ことによってよしなにスケールしてくれるデータベースとして楽に大きなトラフィックを捌くことができるデータベースになってくれる。*2

という感じで、そういった大量のトラフィックと戦うことが多い人にとっては便利なデータベース製品になっている。

しかし、DatastoreにはWebUIこそあるもののCLIツールが提供されていない。

これは地味に不便で、ちょっとしたことをするにもスクリプトを書いて書き換えたり、WebUIからぽちぽちしたりなどする必要がある。 データ量が多いとそもそもそんな簡単にはいかないのだが、開発中にデータ量を気にせずに簡単なデータマイグレーションをするようなケースでもそういうことを要求されるのでちょっと困る。

たとえば、いくつかの限られたデータを他の開発環境からコピーしてきたいようなときでも、そういうスクリプトをわざわざ書かなければならない。 地味に面倒くさい。

dsioというやつもある*3けど、これはマスターデータのインポートとちょっとしたCLIからのデータ参照に便利って感じのもので自分が求めていたものとは少し違った。詳細は本題から逸れ過ぎてしまうので割愛する。

そこで、そういう感じのことが簡単にできるツールを開発した。

github.com

もともと datastore-cli という名前で開発していたが、データストアとのio以外の機能もちょっと入れたくなってきたので dutil という名前に変更した。

dutil io というサブコマンドの下にDatastoreを操作するためのCLIインターフェースが実装されている。

たとえば、あるEntityをキーで指定して取得する場合には dutil io lookup を使う。

% dutil io lookup -p karupanerura 'key(Example, "key1")'
{"key":{"kind":"Example","name":"key1"},"properties":[{"type":"string","value":"string","name":"s","noIndex":true},{"type":"timestamp","value":"2024-02-23T02:23:00Z","name":"d","noIndex":true},{"type":"entity","value":[{"type":"string","value":"value","name":"key"}],"name":"e","noIndex":true},{"type":"float","value":0.5,"name":"f","noIndex":true},{"type":"geo","value":{"lat":0,"lng":0},"name":"g","noIndex":true},{"type":"key","value":{"kind":"Example","name":"key1"},"name":"k","noIndex":true},{"type":"array","value":[{"type":"string","value":"string"}],"name":"a"},{"type":"bool","value":true,"name":"b"},{"type":"string","value":"text\ntext\ntext","name":"t","noIndex":true},{"type":"int","value":123,"name":"i","noIndex":true},{"type":"null","value":null,"name":"n","noIndex":true}]}

このように、入出力はすべてJSON Linesに統一されているのでgrepやsedやjqなどと組み合わせることも簡単。 データ型も明示的なので、たとえばユーザーの自由入力が絡む場面で意図しない型で扱われてしまうといったこともない。

そして、これだけでも jq などで整形して見やすくすることができるが、それでもちょっと面倒なときは dutil io table でJSON形式をテキストテーブル形式に変換する機能も作った。 これは今日GoConで感化されてrange over funcのインターフェースに沿った感じで実装した。

% dutil io lookup -p karupanerura 'key(Example, "key1")' | dutil convert table
+---------+------+--------+------+-------------------------------+-------+-----+-----------------------------------+-----+---------------------+------+--------+------+
| Kind    | Name | a[0]   |    b |                             d | e.key |   f | g                                 | i   | k                   | n    | s      | t    |
+---------+------+--------+------+-------------------------------+-------+-----+-----------------------------------+-----+---------------------+------+--------+------+
| Example | key1 | string | true | 2024-02-23 02:23:00 +0000 UTC | value | 0.5 | geo(lat: 0.000000, lng: 0.000000) | 123 | KEY(Example,"key1") | NULL | string | text |
|         |      |        |      |                               |       |     |                                   |     |                     |      |        | text |
|         |      |        |      |                               |       |     |                                   |     |                     |      |        | text |
+---------+------+--------+------+-------------------------------+-------+-----+-----------------------------------+-----+---------------------+------+--------+------+

こんな感じで表示できる。

他にもGQLをそのまま投げられたりなど様々な機能がある。

そんなわけで、個人的には仕事の中で割と便利に使っています。

Datastoreを使っている皆様方におかれましてはぜひ使ってみて感想を聞かせていただけると嬉しいです。

*1:裏側でレンジシャーディング的なことが行われるので、シーケンシャルなアクセスが発生しにくいように分散させる。

*2:むかしはその代償として制約がかなり厳しかったが、最近はかなり緩和されて使いやすくなっている。

*3:正直、辿り着けなくて後から存在を知ったけど

YAPC::Hiroshima 2024が終わった

yapcjapan.org

終わった……。というのが率直な気持ち。 怪我や事故などの大きなトラブルなく終わることができて本当にホッとしている。

2023年度は仕事も忙しく、かつプライベートも色々あり、とにかく様々が立て続けにあってずっと頭がパンクしていたように思う。 ほかにも様々な状況などが重なった結果、今回はあまりまともにYAPCを手伝うことができなかった。

できたのが杜甫々(とほほ)さんとそーだいさんへの講演オファーをするくらいで、あとは多少の相談に乗ったりくらいで運営としての事前準備は今回は殆ど関われていない。 それでもこれだけ多くの人がYAPCを楽しんでもらえたのは、これを準備してきたコアスタッフの面々と他のJPA理事の面々のおかげだとおもう。本当にありがたい。

「段取り八分、仕事二分」とは言うが、その「二分」の部分だけでも貢献しようと当日はできる限りのことをした。

その結果、当日は様々に気を配り目を配りとしていたところあまり余裕がなく、そーだいさんのトークを聞きにいくことは出来なかったが、 杜甫々(とほほ)さんのキーノートだけはなんとか途中から直接聞くことができた。

これが、本当に面白かった。

他の方も書いていたけど、仕事の合間にやったとさらっと言っているその内容が本当に凄かった。 特に印象に残ったWin32 APIのインターフェースをLinux上に構築する話はWineの一部を自作しているようなもので、 仮にやろうと思ってもそう簡単にできるものではないと思うしこういうことをさらっと「やった」と言えてしまうのはかっこいいと思った。

他にも様々なエピソードを聞くことができた。

そして、最後の質疑応答の答えにあった「好きだから」という言葉は、それまでの流れもあって「『好きだから』ここまでできるんだよ」「『好きだから』続けると色々なものを積み上げられるかもしれないよ」というメッセージとしても自分には響いた。

YAPCという場にお招きできて、自らの言葉でこういったことを語ってもらえたのは自分にとってはもちろん、YAPC参加者もとい日本のPerlコミュニティ*1にとって大きな価値になったと思う。 もちろん、このブログとは別途、杜甫々さん本人にも直接お礼を伝えた。

まだ後始末は終わっていないし次のYAPCの実現に向けて動き出している部分もあり、 目まぐるしいなという感じではあるが無理なくできる範囲で今後もやっていこうと思う。

JPA*2ではYAPCを手伝いたい。自分がYAPCを作りたい。自分こそがYAPCだ。 そんな人々のちからを借りてYAPC::Japanの開催を実現しています。

少しでも興味が湧いた方がいらっしゃったら、自分のTwitter DMやJPAのメール窓口 info@perlassociation.org までぜひお気軽にご連絡ください。

*1:Perlを知ってる人も知らない人もYAPCに参加して楽しめるひとは仲間じゃんPerlコミュニティじゃんっていう感じ

*2:Japan Perl Association YAPCの主催組織

Mustache Templateの実装を書いた

この記事はPerl Advent Calendar 15日目の記事です。

qiita.com

さて

ということで、ひさしぶりにCPANizeしました。なんか早速bug fixが見つかって早々に0.02です。

metacpan.org

今のPCでは初めてのCPANizeだったようで ~/.pause がなくてちょっと焦った。

なんで?

人生色々。様々があります。 たとえば、やることに追われたり、悩ましい考え事に苛まれたり、やることに追われたり、やることに追われたり、様々があります。

一方でISUCONも迫っていました。PerlでISUCONに勝ちたいので日頃の仕事でなかなか書く機会が減ってしまったPerlを素振りしたくなってきます。 気分転換がてら、Perlでちまちまなんかを作ってみるのは良いかもしれないと思いました。

そんななか、趣味でプログラミングするのにちょうど良さそうな題材として、Mustache Templateが出てきました。

Template::Mustacheが1.0系になってめっちゃ遅くなったことに困っていそうな人をみかけたのです。 確かにめちゃくちゃ遅くなったのは知っていて、たしかに調べてみても代替モジュールによさげなのがなかったのです。

まあ簡単そうだしちょうどよいだろう。ミニマルなものを作っていい感じにしよう。と実装しながらMustache Templateの仕様を追っていきます。

すると、なんとも色々な仕様がでてくるではありませんか。

lambdaとか継承テンプレートとかmustacheで1回も使ったことないぞ!などとと思いましたが、それは自分はText::XslateやText::MicroTemplateを使っていたのでまあそれはそう。

まあ必要なひともいるんだろう、そうなるとこれもたぶんほしいんだろう、これを実装するならこれもまあ実装されていて然るべきだろう、などとうっかり色々実装していくと、あれよあれよと芋づる式に全部の仕様を実装してしまいました。

なんで????

実装

Lexerでトークンに分割して、ParserでAST*1を作り、Compilerでコンパイルするという王道な実装となっております。

Lexerといってもそんなに丁寧なものではなくて、素朴なLexerとしてはたとえば "foo{{bar}}" があったときに、これを ["foo", "{{", "bar", "}}"] と分割するのが一般的かと思います。 もうちょっとParser寄りな感じで、["foo", "{{bar}}"] という感じでタグの構文そのものだけ解釈してトークン分割をする実装にしました。

これにはちょっと理由があって、Mustacheって{{}}というデリミタでタグを構成するのがデフォルトなのですが、このデリミタを変更するタグが存在するので、Lexerはそれを解釈しないと正しくトークン分割ができないのです。すると、まあそこまでしてるのに {{bar}} を分ける必要もなかろうということで自ずとそれらを一緒のトークンにまとめて扱うことにしました。

そして、Parserはその一列のトークンを解釈してMustache Templateとしての文法の構造をデータ構造上の構造に構造化します。いわゆるASTというやつですね。 たとえば、Mustacheには{{#condition}}yes{{/condition}}みたいな複数のタグを使って1つの構造を作るみたいな文法をもっているので、これはAST上もこの分岐の子要素みたいな感じで整理してあげます。

ASTを使うとめっちゃ簡単にレンダリング処理が実装できます。 素朴にレンダリングする事もできると思いますが、今回はせっかくなのでキャッシュしたテンプレートの実行効率を高めるべくCompilerとして実装しようと思います。

Compilerってなんやねんとなると思いますが、これはText::MicroTemplateよろしくテンプレートに合わせたパターンの文字列を生成するPerlのコードを文字列で作り上げてevalすることで、事前に分岐などが最適化されたコードを作る操作を指しています。

Perlコードの生成時はASTを解釈して細かい分岐をたくさん辿りますが、生成されたPerlコードはある程度の分岐が事前に解決されたものになるので直にASTを毎度解釈するより高速に実行することができるという寸法です。

Apache::LogFormat::Compilerなど様々なCPANモジュールでこのアプローチは取られています。 なんなら、こういうコードを簡単に作るためのSub::Quoteってやつまであり、これはMooの実装で使われていたりもします。

ほか、render というテンプレートをパースして即レンダリングするというインターフェースもあるんですが、このケースではコンパイルしたものが1度実行したら廃棄される前提がありかつコンテキスト変数*2がコンパイル前にすでに決定しているという特徴があるので、コンテキスト変数をヒントにASTを最適化することが可能です。 ということで、そういう処理を挟んであり、うまくいくケースではこの最適化をやらない場合と比べて3倍くらい速くなるようになることがわかっています。

ほかにも、正規表現を最適化していたりなど、地味な高速化を測っている箇所がちょいちょいあったりします。

出来栄え

github.com

このテストケースをすべてPASSしています。やったね。 ついでにテストカバレッジもステートメントレベルでは100%*3で、ほかも90%台後半まで網羅してあります。

パフォーマンスも簡単なテンプレートをレンダリングするベンチマークで比較してみました。 Template::Mustacheに比べてパースの速度が10倍、レンダリングする速度がパース済のテンプレートをキャッシュしているケースで3倍、キャッシュしていないケースで40倍となることがわかっています。 やったね。

なお、Mustache::Simpleというモジュールがあるのですが、これはテストケースをpassしなかったので比較していません。(存在しない変数を無視せずにエラーにしてくる仕様非準拠な挙動だけ確認してあとは確認してない)

ということで、まあそれなりに実用に耐える品質ではあろうと思ったのでCPANnizeしておきました。

これを作る時間でISUCONの素振りすればよかったのでは?

ISUCONの素振りってまとまった時間が必要じゃないですか……。 これ細切れの微妙な時間でちまちま進めたのです。素振りもしたかった……。

そんなわけで

まあ、完成したのでもしご入用の方はお試し下さい。

*1:Abstract Syntax Tree

*2:テンプレートに与える変数

*3:普通は絶対にこのパスは通らないところとかは除いているのでちょっとズルかも

ISUCON13にPerlで挑んだ

この記事はPerl Advent Calendar 14日目の記事です。

qiita.com

チーム「銀河鉄道の昼」としてISUCON13にPerlで挑みました。 最終的に2台構成で最終スコア7083、最高スコアは1台構成での9381でした。

上位30チームが50000点弱以上なので、残念ながら惨敗と言って差し支えない結果です。 くやしい……。上位チームの皆さんおめでとうございます。

何をやったのか

なんやかんや、足回りを整えたりトラブルシューティングしたりで2時間くらい食ってたきがします。

StatsHandlerのN+1解消

順位を計算する処理があり、その順位とはtipの総額とリアクションの総数によって順位付けられていました。 この際のもとの実装は以下のような感じです。

    # ランク算出
    my $ranking = [];
    for my $livestream ($livestreams->@*) {
        my $reactions = $app->dbh->select_one(
            q[
                SELECT COUNT(*) FROM livestreams l
                INNER JOIN reactions r ON r.livestream_id = l.id
                WHERE l.id = ?
            ],
            $livestream->id
        );

        my $total_tips = $app->dbh->select_one(
            q[
                SELECT IFNULL(SUM(l2.tip), 0) FROM livestreams l
                INNER JOIN livecomments l2 ON l2.livestream_id = l.id
                WHERE l.id = ?
            ],
            $livestream->id
        );

        my $score = $reactions + $total_tips;
        push $ranking->@* => Isupipe::Entity::LivestreamRankingEntry->new(
            livestream_id => $livestream->id,
            score => $score,
        );
    }

    my @sorted_ranking = sort {
        if ($a->score == $b->score) {
            $a->livestream_id <=> $b->livestream_id;
        }
        else {
            $a->score <=> $b->score;
        }
    } $ranking->@*;

    $ranking = \@sorted_ranking;

    my $rank = 1;
    for (my $i = scalar $ranking->@* - 1; $i >= 0; $i--) {
        my $entry = $ranking->[$i];
        if ($entry->livestream_id == $livestream_id) {
            last;
        }
        $rank++;
    }

うーん、では中間テーブルを作りましょう。

RedisのSortedSetを使うことも考えましたが、scoreが同順であった場合はlivestream_idの昇順となるように実装されているためそのへんをうまくやれるだけのRedis力が僕にはなかったです。 (出来ないと思っていた。実はできるという話を聞いたけどどうやってやるのか知らないので知っているひとはコメントで教えてください。)

中間テーブルをこんな感じで作って

-- スコア
CREATE TABLE `user_scores` (
  `user_id` BIGINT NOT NULL PRIMARY KEY,
  `score` BIGINT NOT NULL,
  `user_name` VARCHAR(255) NOT NULL,
  INDEX ranking_idx (`score` DESC, `user_name` DESC)
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

CREATE TABLE `livestream_scores` (
  `livestream_id` BIGINT NOT NULL PRIMARY KEY,
  `score` BIGINT NOT NULL,
  INDEX ranking_idx (`score` DESC, `livestream_id` DESC)
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

更新時によしなに更新していくと、以下のような感じで順位がSQLだけで取得できるようになりました。

    $app->dbh->query('SET @r = 0');
    my $rank = $app->dbh->select_one(q!SELECT `rank` FROM (SELECT livestream_id, score, @r := @r+1 AS `rank` FROM livestream_scores ORDER BY score DESC, livestream_id DESC) a WHERE a.livestream_id = ?!, $livestream_id);

Window関数を使って DENSE_RANK() でなんとかする手もあるかもでしたが、うまくインデックスが効かないのでこういう感じにしました。

たしかまあまあスコアは上がった気がします。 しかし、なんやかんやハマって2~3時間くらいこれで溶かしてしまった気がします。

reservation slotsのチューニング

元々の実装はこんな感じでした。

    # 予約枠をみて、予約が可能か調べる
    # NOTE: 並列な予約のoverbooking防止にFOR UPDATEが必要
    my $slots = $app->dbh->select_all_as(
        'Isupipe::Entity::ReservationSlot',
        'SELECT * FROM reservation_slots WHERE start_at >= ? AND end_at <= ? FOR UPDATE',
        $params->{start_at},
        $params->{end_at},
    );

    for my $slot ($slots->@*) {
        my $count = $app->dbh->select_one(
            'SELECT slot FROM reservation_slots WHERE start_at = ? AND end_at = ?',
            $slot->start_at,
            $slot->end_at,
        );
        infof('%d ~ %d予約枠の残数 = %d', $slot->start_at, $slot->end_at, $slot->slot);
        if ($count < 1) {
            $c->halt(HTTP_BAD_REQUEST, sprintf("予約期間 %d ~ %dに対して、予約区間 %d ~ %dが予約できません", TERM_START_AT, TERM_END_AT, $params->{start_at}, $params->{end_at}));
        }
    }

この手のはロックする範囲をいかに絞れるかがキモっぽいという直感と、幸いにもslotは減ることはあっても増えることがない仕様であることがわかったため、 すでに空になっている予約枠を予め除外してロックを取ることでロックの競合をなるべく抑えるようにしてみました。

    # 予約枠をみて、予約が可能か調べる
    my %slots = @{
        $app->dbh->selectcol_arrayref(
            'SELECT id, slot FROM reservation_slots WHERE start_at >= ? AND end_at <= ?',
            { Columns => [ 1, 2 ] }, $params->{start_at}, $params->{end_at},
        )
    };
    if (any { $_ == 0 } values %slots) {
        $c->halt(HTTP_BAD_REQUEST, sprintf("予約期間 %d ~ %dに対して、予約区間 %d ~ %dが予約できません", TERM_START_AT, TERM_END_AT, $params->{start_at}, $params->{end_at}));
    }

    my $txn = $app->dbh->txn_scope;

    # NOTE: 並列な予約のoverbooking防止にFOR UPDATEが必要
    my $overed_slots = $app->dbh->select_one('SELECT COUNT(*) FROM reservation_slots WHERE id IN (?) AND slot = 0 FOR UPDATE', [keys %slots]);
    if ($overed_slots) {
        $c->halt(HTTP_BAD_REQUEST, sprintf("予約期間 %d ~ %dに対して、予約区間 %d ~ %dが予約できません", TERM_START_AT, TERM_END_AT, $params->{start_at}, $params->{end_at}));
    }

これも、たしかまあまあスコアは上がった気がします。

どうして複数台構成にした途端にスコアが落ちたのか

MySQLを2台目に追い出したのですが、どうやらその途端にDNS Serverのスループットが改善してDNS水責め攻撃の餌食となったようです。 2台構成が動いたのが終了10分前とかだったのでそこから元に戻すのは諦めました……。

次回に向けて

今回は素振りがほとんど出来なかったので素振りする時間をなんとか確保して、トラブルシューティングに費やす時間を減らし、チューニングの手札を増やしておきたいです。 今回はnginxでキャッシュさせるといったこともほとんど出来ず、またnginx luaを使えば楽にいきそうな部分もあったのでそういったこともできるようにしていきたい。

次回もPerlで挑む覚悟で、実装移植がなかったら自前でいちからコードを書いてでもPerlで戦おうと思います。

感想

移植はモダンなPerlの機能がふんだんに使われており、面白い実装でした。(Entityクラスは正直いらなかった気がしますが……。) 問題もボリューム満点で面白く、素朴なチューニングでスコアがちゃんと上がるような問題に仕上がっていてスコアが上がる楽しさも感じました。 運営の皆さんには感謝の気持ちでいっぱいです。ありがとうございました。