かおるノート

cordx56のブログです

mixiさんでおしゃべりロボットを作っておりました

こんにちは。 確定申告が無事終了し、安堵しております。 みなさんはいかがお過ごしでしょうか? そろそろ新生活という方も多いのではないかと思います。 みなさんの新生活、応援しております。

さて、今回は株式会社ミクシィさんにインターンで参加させていただいておりましたので、そのことについて書いていきたいと思います。

応募

元々mixiさんとは、大学のサークルに来ていただいていた就活エージェントさんにBSCという1dayイベントを勧められたという経緯で関係がありました。 その後、インターンシップ情報が集まるスプレッドシートmixiさんが記載されていたところから、インターンに応募させていただきました。 mixiさんの手がけている事業などは全然知らなかったのですが、求められているスキルと自分のスキルセットが合っていたこと、そして時給がそれなりに良かったこと1などが応募の決め手になりました。

面接は4回あり、人事面接、1回目のエンジニア面接、2回目のエンジニア面接、配属先部署面接、と言った感じでした。 インターンの面接で4回も面接をしたのは初めてだったので、驚きました。

配属

記事タイトルの通り、Romi事業部というところで、おしゃべりロボットの開発をしておりました。 Romiというのは、mixiさんのRomi事業部というところで開発されているおしゃべりロボットで、Romiに話しかけて会話を楽しんだり、Romiの方から話しかけてくれたりします。 mixi自体はバリバリWeb企業風という印象があったので、ロボットの開発をしているというのは少し驚きました。 ただ、ロボットだから組み込み、というわけではなく、Romiの会話を司る部分はAPIサーバを叩くようになっており、私はサーバサイドの開発をさせていただいておりました。

配属先については、事前の面談でご相談させていただいていて、私がPythonの研究をしていることなどからサーバでPythonを利用しているRomi事業部が気になるという旨を伝えて、配属先部署面接を組んでいただいた、と言った感じでした。

開発環境

開発環境は、入社時に貸与されるパソコンをWindows機かMacBookで選べると言った感じでした。 私はUS配列のMacBookを選んで、M1のMacBook Airが貸与されました。 ただ、Romi事業部では手元では開発は行わず、AWSのEC2上のUbuntuで開発を行なっていたので、MacBookはほとんどSSHとブラウザを利用するためにしか使っていないと言った感じでした。 開発環境はUbuntuのバージョンがちょっと古くて、aptで普通に入るNeovimがちょっと古いバージョンだったこと以外で困ったことは特になく、快適に開発することができました。

オフィス

mixiさんのインターンでは、こういった状況下ではありますが、リモートでも出社でも良いという方針だったので、週1回、メンターさんとの面談の日には出社する、といった感じでオフィスに通っておりました。

mixiさんのオフィスは、渋谷スクランブルスクエアという渋谷駅直結のビルにあり、28階から36階までがmixiさんのフロアという感じです。 35階には社食とカフェが入っており、安い価格で昼食を食べられたり、コーヒーはもちろん、チョコレートドリンクやアサイースムージーなどを飲むこともできます。 普通にコーヒーが飲みたいのであれば、各フロアにコーヒーメーカーが置いてあるので、いくらでも無料で飲むこともできました。 また、mixiでは各自にハーマンミラーアーロンチェア2KOKUYOの電動昇降デスクが用意されており、非常に快適に業務にあたることができました。

課題

課題は、最初に比較的簡単なチュートリアル的な課題をいくつか用意していただき、そのあとは積んであるタスクの中から、自分の気になった課題を選んで取り組むと言った感じでした。 私は一般的なサーバサイドエンジニアとしてインターンに参加させていただいたので、機械学習の部分には触れず、ルールベースで会話を行う部分の実装をひたすらやっておりました。

Romiではシナリオエディタというのを使って、Romiのルールベースの会話をカスタマイズすることができます。 ルールベースの会話では、モジュールというのを使って、さまざまな機能を実現しています。 例えば、 {owner_name} というモジュールを利用すると、シナリオの中で、オーナー(Romiのユーザのことです)の名前を読み上げることができます。 こういったモジュールの開発を中心に課題に取り組みました。

課題に取り組むにあたってはメンターさんや事業部の方々に色々教えていただいて、困ることなく課題に取り組むことができました。

成果発表会

最終日はオフィスの会議室を一室押さえていただき、成果発表を行いました。 15分ほど成果発表を行い、さらに10分くらい質疑応答といった感じで、30分ほど時間をとっていただきました。

成果発表会の最後に、メンターさんやマネージャーさんからは「とても手が速かった」「面接の時は技術全振り人間かと思ったが、ちゃんとサービスのことを考えて実装していて良かった」といったコメントをいただきました。 自分はそんなに手が速い方ではないかなと思っていたり、企画やサービスのことを考えながら実装することの難しさなどを感じていたので、そう言っていただけたことはちょっと驚きでもありました。

宣伝

というわけで、開発に参加させていただいたRomiですが、現在キャンペーンで公式ストアからの購入で本体価格が10%オフ、月会費が3ヶ月分無料になるそうです。 ロボットと会話してみたい、かわいらしいロボットが欲しい、といった方にはとても刺さるプロダクトになっているのではないかと思います。

ご購入や詳しくはこちらを参照していただければと思います。 ぜひご検討ください!

さいごに

mixiさんでの1ヶ月半は長いようで短いものでした。 mixiの、特にRomi事業部の皆様には大変お世話になりました。

これからもどうぞよろしくお願いいたします。


  1. 正直なところ、時給が悪かったら申し込んでいなかったんじゃないかなと思うくらいは時給がよかったです

  2. 一脚20万円以上する非常に有名なオフィスチェアです。最初に見た時は目を疑いましたが、社員さんに聞いたところアーロンチェアで間違いないとのことで、びっくりしました。

ピクシブさんでインターンをしておりました

こんにちは。 あけましておめでとうございます。 よく冷える日が続いていますね。 皆様も体調には気をつけてお過ごしください。

さて、タイトルの通り、昨年11月から今年1月までピクシブ株式会社さんでインターンをさせていただいておりました。 今回はピクシブさんでのインターンについて振り返る記事を書こうかと思います。

応募

元々は夏に開催されていた短期のインターンに申し込み、人数の関係でそのインターンには参加できず、ということだったのですが、ピクシブさんの方から冬または春のインターンか長期インターンもしくはアルバイトでの参加を提案していただき、最終的に長期インターンで参加させていただくこととなりました。

配属

配属先の部署決定に際しては、私の興味を持っていることなどを面談でしっかり確認していただきました。

最初はGoなどの静的型付き言語を扱っている部署が良いかななどと思っていたのですが、面談などで私がPythonの型注釈の研究をしていることなどを話したことから、ソースコードの静的解析を行ってる部署への配属をご提案いただき、その部署へ配属ということになりました。

開発環境

開発環境はフルリモートだったため、LinuxAmazon WorkSpacesでした。 PhpStormのライセンスを割り当てていただき、ほとんどの開発をPhpStorm上で行うといった感じでした。

慣れない環境ではありましたが、そんなに苦労することもなかったなという印象です。

課題

課題については、メンターさんと相談しながら、そう長くはないインターン期間を有意義に過ごせるようにと課題を設定していただきました。 課題のほとんどはpixivやピクシブ百科事典といったサービスの開発を静的解析で支援するといった内容で、非常に面白かったです。

取り組んだ課題の中で一番面白く達成感があったのは、PHPの静的解析ツールであるPHPStanの拡張を書く、という課題でした。 pixivでは、ユーザ情報の取得を行う関数があり、その引数に渡すことによって返り値が変わるオプションが存在しているのですが、そのオプションの値を見て、返ってくる連想配列の型が変わるのをPHPStanに教える、というものでした。

例えば、次のようなユーザデータの取得を行う関数があるとすると、

User_Common::getGenericDataById(11)
// array{user_id: string, user_status: string, user_account: string, user_name: string}|false

返り値の型はコメントで示したようになります。

この関数には第二引数にオプションを渡すことができ、オプションによって返ってくる連想配列が変わります。

User_Common::getGenericDataById(11, ['expand_serialized_field' => true, 'additional_fields' => ['serialized']]);
// array{user_id: string, user_status: string, user_account: string, user_name: string, user_contact: array{Twitter: string}, show_foobar: bool, foobar: array}|false

返り値の型はコメントで示したようになります。

こういったオプションによる型の変化は通常の静的解析では知ることができず、詳細な型をつけることができません。 実際、現状のpixivのソースコードでは array|false としか型をつけられていませんでした。

PHPStanの拡張を書くことにより、このオプションの値が静的に求まる場合は、詳細な型をPHPStanに教えることができます。

ということで、実際にPHPStanの拡張を書き、ユーザ情報の取得に関わる関数について、詳細な型をつけることができました。

PHPを書いたのはかなりしばらくぶりで、静的解析となると全く触ったことがなかったですが、メンターさんに優しく詳しく教えていただき、たくさんの学びがありました。

最終課題発表

最終日には取り組んだ課題について発表する場を設けていただきました。 私が取り組んだ課題はPHPStanを利用したPHPでの静的解析に関するものがほとんどだったため、「静的解析とpixiv開発支援」という課題テーマで発表させていただきました。

さいごに

ピクシブさんでのインターンは面白く、多くの学びが得られたインターンでした。 ピクシブの皆様、長いようで短かった三か月間、ありがとうございました。

これからもどうぞよろしくお願いします。

Tweet generatorのサーバをVPSからAWSに移行しました。

こんにちは。 急に秋の気配がやってきましたね。 研究が進んでおらず冷や汗をかいております。

今回はこれまでVPS上で運用していたTweet generatorAWSに移行したので、そのことについて書いていきます。

Tweet generatorはこれまで約50万ユーザの方に支えられ、小さいプログラムながら大きなサービスへと成長することができました。 その過程で、可能な限り運用費をケチったシンプルなVPSでの運用に限界を感じたことは多々ありました。 ストレージ逼迫による約37万ユーザ分の学習データの破棄、アクセスが多かった時期には読み込みが非常に遅いなどの問題がありました。 しかしながら、Tweet generatorも収益化を行ってから1年以上が経ち、得られた収益により様々な恩恵を受けることがありました。 収益化を行っているからには、私にはこのサービスをユーザの皆様に快適に利用していただけるよう努める義務があると思っております。

今回はそんな気持ちを抱きながら、より安定した長期稼働の為に行ったAWSへの移行を、検討から実際の移行についてまとめました。 またTweet generatorの話かよ、と思われるかもしれませんが、まぁ結構大変だったので聞いてくださるとありがたいです。

検討

まずは移行の検討を行った背景について説明します。

まず、これまでTweet generatorを運用していたサーバは、私の公開しているほとんどのサービスの運用を一手に担うConoHaのVPSサーバでした。 ConoHaは学生にやさしく、10%割引で各種サービスを利用することができます。 このサーバ上では常時5つ程度のWebサービス、Discord botなどが動いており、CPUリソースもメモリリソースも常時それなりに逼迫しているという状況でした。

この段階ですでにやばいのですが、2020年2月頃、急にTweet generatorへのトラフィックが増加し始め、一番ひどいときは通常時500ミリ秒以内程度のテキスト生成に数十秒かかるほどにサーバを圧迫していました。

そしてそれなりに重めの学習済みマルコフ連鎖モデルは50GBしかなかったVPSのストレージを即座に食い尽くし、何度かの学習済みマルコフ連鎖モデルの破棄とストレージの追加契約とVPSのアップグレードを余儀なくされました。 この時点ではまだTweet generatorは収益化していなかったため、アクセスが来れば来るほど私が損するという状況にありました。

そして2020年5月頃、友人が広告とか置けばいいんじゃないかと言っていたのをふと思い出し、収益化を行いました。 結果として、すぐにサーバ代をペイするくらいの収益が得られるようになりました。

この時点ではまだサーバ代をケチる精神が健在だったため、この1台にできるだけサービスを詰め込む運用でしばらく運用を続けていました。 Tweet generator自体の設計のやばさもあったため、2020年12月頃には学習済みマルコフ連鎖モデルの管理をファイルベースからPostgreSQLでの管理に切り替え、また学習済みマルコフ連鎖モデルの破棄を行いました。 こうして、つい先日までのVPSでの運用が続きました。

しかしながら、私自身業務でAWSに触る機会が増え、AWSでの堅牢で可用性の高いサービス運用について知見を深めていくと共に、Tweet generatorの運用がやばいということを強く感じるようになりました。 また、個人的に仕事などが増えたことから金銭的な余裕が生まれ、AWSをポケットマネーで自由に触ってみようと思い、趣味でもAWSを触ることにしました。

こうしてAWSでの運用の知見が蓄積されていき、AWSでの運用を検討するに至りました。

AWSでの運用を検討した際に、まずうれしかったのは、またアクセスが集中することがあっても、オートスケールする設計が簡単に組めることでした。 また、もしまたアクセスが集中した場合、その場合はストレージの逼迫が考えられたので、ストレージ容量も簡単にスケールするようなDBが求められました。

AWSはこれらの点をすべて満たしてくれています。

また、他社サービス、具体的にはGCPなども検討しましたが、AWSで求められる機能は実現でき、かつ業務などでAWSを使うことが多いのにわざわざGCPを使うこともないだろうと思い、AWSで運用することを決めました。

要件

今回の移行で求められた要件を書いていきます。

ダウンタイムは最小に

まず今回の移行にあたっての目標は、ダウンタイムを最小にして移行させることでした。

Tweet generatorにはおおよそ毎秒リクエストが飛んできています。 また、ダウンタイムが長引けば、広告収入減につながります。

以上の事情から、ダウンタイムは最小にすることが求められました。

データ損失なく

これは完全に趣味として、データ損失なく移行を進めたいという気持ちがありました。 技術的にも金銭的にも、かかる費用を考えればこれまでのデータを破棄して、まっさらなDBでサービスを継続するのが楽でした。

しかし、実際の企業のサービスなどではそんなことは言ってられないでしょう。 折角の機会なんだからデータ損失なく移行させられたら面白いじゃん?と思い、自分の技術力の範囲でできるだけデータ損失なく移行させようと思いました。

移行

ここからは、実際の移行にあたって発生した問題などについて書いていきたいと思います。

コンピューティングリソースの移行

これは普通にEC2上にdocker-composeでコンテナ群を立ち上げる方式で、EC2インスタンスをオートスケーリンググループで立ち上げてやることにしました。 負荷分散は普通にALBでやってあげている感じです。

コンピューティングリソースの移行はそんなに苦労しなかったのですが、唯一苦労した点と言えば、Graviton、つまりaarch64なAWSインスタンスへのdocker-composeのインストールでした。 docker-composeの公式はaarch64向けのバイナリを配布していません。 結論から言うとこちらの回答を参考にしたらすんなりと行きました。

何故苦労してまでaarch64を使いたかったのかというと、価格とパフォーマンスが良かったからです。 詳しくはこちらの記事がわかりやすくてよいでしょう。 Tweet generatorの場合だと、t3.microを時間単価0.0104USDで使うところを、t4g.microを時間単価0.0084USDで使うことができます。 もちろん、性能の比較は難しいですが、単純にvCPU数とRAM容量を見た限りでは、この二つに差異はありません。 また適当な機会にt3.microも導入して、ALBレスポンスタイムやロードアベレージがどうなるかを調査するなどして、性能については詳しく追っていきたいと思っています。

DBの移行

DBはVPS上でDockerを使い立ち上げていたPostgreSQLからRDSのPostgreSQLに移行しました。

結論から言うと、DBの移行には普通にSQLダンプを使って移行した後、自前で作ったマイグレーションツールを使って細かい差分を吸収してやりました。

DMSとの闘い

当初はAWSのサービスであるDatabase Migration Service、DMSを用いてDBの移行を行う予定でした。

しかし、DMSでは学習済みマルコフ連鎖モデルを保持しているテーブルの移行に何故かエラーも吐かずに終了してしまいました。 これは全くの原因不明で、この調査の為に数日を溶かしたのですが、結局何もわからず、DMSでのDB移行を諦めました。

ダンプファイルとの闘い

結局、普通にダンプを利用することにしました。 まずは移行元データベースのダンプを取ってきて、ダンプしたSQLを利用してRDSにデータを突っ込んでやります。 このダンプファイルが294GBありました。 294GBも何を保持しているんですかね……

ダンプファイルを今度はRDS上のPostgreSQLに突っ込みます。 この作業に162時間かかりました。 皆さんがSQLを話し終わるまで162時間かかった校長先生かな?

さらにこの手続きが終わった後、ダンプからの移行中に発生した移行元DBの変更を移行先DBに反映してやるために、お手製ツールで移行元DBと移行先DBの差分を取ってきて反映、みたいなことをしました。

これだけの時間、これだけのリソースを消費して移行したデータなので、十分に活用していきたいですね1……

DNSの移行

最後に行ったのはDNSの移行です。

移行に伴いTweet generatorのドメイン名を変更することも考えたのですが、前回ドメイン名を変更したときに、既にTwitter上には古いドメイン名でのリンクが大量に転がっていたため、古いドメイン名からのリダイレクト処理をずっと走らせる必要がありました。 今回もそれをやるのは避けたかったこともあり、同じドメイン名でサーバの移行のみをすることにしました。

基本的に行ったことは、これまでVPSに向いていたAレコードをALBのドメイン名のCNAMEに置き換えてあげることだけでした。

しかし、DNSは基本的にTTLの間はキャッシュがきくので、移行直後は移行元サーバと移行先サーバの両方にリクエストが来ます。

この際、特に対策を取らないと移行元でのDBへの変更は移行先DBへ反映されません。 そのため、事前にAWSのセキュリティグループでVPSIPアドレスからRDSへのアクセスを許可するように設定しておき、移行のタイミングで移行元サーバのアクセス先DBをRDSに変更することで、移行のタイミングで移行元サーバと移行先サーバ共にRDSにアクセスするように変更しました。

こうすることで、DNSの移行期間中も、データを失うことなくサービスを継続させることができました。

結果

結果として、ダウンタイム2時間以内で、ユーザテーブルとマルコフ連鎖モデルテーブルに関しては2データ損失なく移行を終えることができました。 いや結構ダウンしとるな?

ダウンタイムは、停止後に最終のデータ移行を行ったのと、移行後のデータベースに異常がないかの確認、特にsequenceテーブルが壊れてしまっていたのでそれの修正と、移行元サーバのアクセス先DBの変更の為にどうしても必要になりました。

最後に

長くなりましたが、Tweet generatorのAWSへの移行はこんな感じで行いました。

サーバ移行を真面目にやったのは初めてだったのですが、なかなか苦労しました。 今後はもうあまりやりたくないですね……

もしこの記事が今後AWSに触れる方などの参考になることがあれば幸いです。

最後になりましたが、これからもどうぞよろしくお願いいたします。


  1. まぁ活用法がなくて困ってるんですが

  2. 生成ログデータとセッションデータは保持することによって得られる利点に比べて移行が大変だったので、そこは無視することにしました

DMM GUILDというインターンに参加しました

こんにちは。 もう夏も終わりですね。

さて、タイトルの通りですが、つい先日まで合同会社DMM.comさんのインターンであるDMM GUILDに参加しておりましたので、今日はそのことについて書いていきたいと思います。

DMM GUILDとは

DMM GUILDとは、合同会社DMM.comさんのインターンになります。

詳しくはこちらのページを見ていただくのが良いかと思いますが、簡単に説明すると、DMMさん側から様々な部署・事業を横断してたくさんの課題が出され、課題をクリアすると課題に応じたポイントが獲得でき、入賞者には賞金まででる面白いインターンになっています。

今年は2週間全日リモート開催とのことで、おうちから参加させていただきました。 六本木行ってみたかったわね……

応募

応募の時のことはあまりよく覚えてないのですが、エントリーシートのようなものを書いて送った記憶があります。

その後1回面接があり、自分の経験などについてお話をするといった感じでした。

開発環境

開発環境としては、Intel CPUのMacBook Proが貸与されました。 キーボードも英字とJISとを選ぶことができ、また、希望者にはディスプレイも貸与されるようでした。

課題

課題はGitHubのIssue形式で提示され、それをもとにやっていくという感じでした。

内容としては実際のDMMさんのプロダクトを改善していくもので、あまり詳しくは書けないのですが、多事業・多分野の中から自由に選択して課題に取り組むことができました。

私はMacBookで開発をした経験がほとんどないため、この機会にとiOSの課題を1つ取ってみました。 Swiftは初めてでしたが、社員さんのレビューでより良い書き方を教えていただいたりして、勉強になりました。

あとはバックエンドの課題やAWSなどのインフラ周りの課題にも取り組みました。

あまりいい成果は上げられませんでしたが、社員さんにしっかりレビューしていただき、貴重な経験になりました。 また、技術的にも成長できたなと感じました。

面談

期間中には3回ほど面談が組まれており、人事の方にDMM GUILDでの目標や達成度合い、要望などを聞いていただきました。

2週間のうちで3回も面談の時間を取っていただき、丁寧にこちらのことを聞いていただき、とてもありがたかったです。

その他

その他には、実際にDMMさんが利用しているSlackワークスペースに入れていただき、timesチャンネルを作ってインターン生同士、中には社員さんも入って、活発に近況報告や情報交換がされていました。 私のtimesチャンネルは何故か1日1回ダジャレを言うコーナーになってしまったので、私が毎日ダジャレを言っていくなど、和気あいあいとした様子でした。

f:id:cordx56:20210910183606p:plain
最終日には別のインターン生の方にダジャレをまとめていただきました

また、DMM社員さんとの会食やビアバッシュというLT大会にも呼んでいただきました。

会食やビアバッシュでは質問に答えていただいたり、面白い話をしていただいたり、DMMの裏側について色々教えていただいたりしました。

最後に

DMM GUILD、とても面白いインターンでした。

DMM社員の皆さん、そしてインターン生の皆さん、短い間でしたが本当にありがとうございました。 この経験をもとに引き続き頑張っていきたいと思うので、これからもどうぞよろしくお願いします。

謎の中華ボードにOpenWrtを突っ込んで10Gbpsルータを組んだ話

こんにちは。 最近人々にノベルゲーの沼に突き落とされました。

それはそうとして、皆さん10Gbpsしたいですよね? 私はしたいです。

しかしながら10Gbpsを出せる機械はそう多くありません。 それが様々なことができる高性能なルータで、SFP+でともなれば尚更です。

そんなことを思っていたある日、私の欲求を満たす謎の中華ボードを発見しました。 それがこのOK1046A-C2です。

www.forlinx.net

10GbpsのSFP+を2ポート、1GbpsのEthernetを5ポート積んでおり、その他にもWiFiモジュールを接続できるポート、5Gモジュールを接続できるポート、USB3.0と十分なインターフェースを備えています。

UbuntuとLEDE(OpenWrt)が動くとのことで、ソフトウェア面もばっちりです。

今回の記事は、そんな高性能中華ボード、OK1046A-C2との格闘1とその性能についてお話ししようと思います。

購入までの道のり

購入はアリババが公式の窓口のようです。

www.alibaba.com

アリババに登録して、Contact Supplierボタンを押すと、問い合わせができます。 必要量とこちらの要求項目などを軽く記入して、問い合わせましょう。 私の場合は問い合わせるとすぐに返信が来ました。 要求項目などについて英語でやり取りをして、製品がこちらの要求を満たしているかなどを確認してくれたのち、名刺を送ってもらい、メールアドレスを求められたので返答して、あとはメールでやり取りをしました。

私は1枚の購入だったので、見積もりを出してもらったら$500ということになりました。 まぁスペックを考えるとそんなもんだろうと、ここ最近NASの購入と10Gbps環境の整備で完全に緩み切っていたお財布のひもがさらに緩んで、購入に至りました。 支払いはPayPalで済ませることができ、問い合わせから購入までかなりスピーディーに進みました。 輸送はFedExで、すぐに届きました。

ドキュメント

製品の発送連絡と一緒に、Dropboxの共有リンクでドキュメントを共有してもらいました。 ドキュメントは充実とまでは言いませんが、必要最低限の情報は載っているという印象を受けました。 とりあえずドキュメントが片手にあれば、OSのインストールまでは何の苦労もなくできるといった感じです。

OpenWrtのインストール

ドキュメントにはちょっと古めのLEDEのスナップショット版のイメージが同梱されており、必要なファイルと共にUSBメモリに入れてOK1046A-C2のUSBポートに挿して起動するだけで、インストールが完了します。 これをそのまま使ってもいいのですが、ちょっと古めのLEDEのスナップショットということで、libcが入らなかったり、カーネルが古くてインストールできないパッケージがあったりと、微妙に使いづらいです。

そのため、このLEDEを最新のOpenWrtへとアップグレードしようとしたのですが、ここでちょっとハマりました。

OpenWrtのビルド

まずはOpenWrtをLS1046A向けにビルドします。 まずはopenwrt/openwrtをクローンしてきて、ディレクトリに入ります。 ここから config.buildinfo をダウンロードしてきて、 .config として保存します。 そして、 $ make menuconfig でターゲットのデバイス、出力したいイメージなどを選択します。 それが終わったら、 $ make でビルドします。

ビルド環境の構築には、このDockerfileを利用させていただきました。

ext4イメージが出力できない

ext4のイメージが直接ビルドできればいいのですが、私の手元ではうまくいきませんでした。 ここを見ると、おそらくイメージ容量が大きすぎてビルドが落ちてしまっているのだと思いますが、ログを調べても怪しいところはなく、何故うまくビルドされないのかはいまだに不明です。

しかしながら、tar.gzファイルはビルドできました。

ext4イメージがどういう構成になっているのかがわかれば手元でtar.gzファイルの中身をext4イメージに移植してあげたのですが、ext4イメージがどういう構成になっているのかもわかりませんでした2

ここで、ある考えが頭をよぎります。

tar.gzファイルをルートに展開してやればよいのでは?

LEDEからOpenWrt 19.07.8へのアップグレード

ということで、scpコマンドでOK1046A-C2へビルドしたtar.gzファイルを転送します。

それができたら、tar.gzファイルをルートに展開します。

# tar -zxvf openwrt-19.07.8-layerscape-armv8_64b-device-ls1046ardb-rootfs.tar.gz -C /

ここまで出来たら再起動します。

再起動が終わると、古かったLEDEのスナップショットがOpenWrt 19.07.8にアップグレードされています。

こうして無事OpenWrt 19.07.8が使えるようになりました。

今のところこれで問題なく使えています。

性能について

CPUのアーキテクチャはCortex-A72で1.8GHzクアッドコア、RAMは2GBと、ルータとして使うには十分な性能でしょう。

問題は速度です。 10GbpsのSFP+ポートがついてはいますが、実際のところどれくらい速度が出るのかは気になるところだと思います。

iperf3を使ってSFP+で接続している端末とOK1046A-C2間の速度を測ってみることにしました。

OpenWrtへのiperf3のインストールはopkgで簡単にできます。

# opkg update
# opkg install iperf3

iperf3の実行結果は次のようになりました。

[ ID] Interval           Transfer     Bandwidth
[  4]   0.00-1.00   sec   634 MBytes  5.31 Gbits/sec
[  4]   1.00-2.00   sec   572 MBytes  4.80 Gbits/sec
[  4]   2.00-3.00   sec   640 MBytes  5.36 Gbits/sec
[  4]   3.00-4.00   sec   653 MBytes  5.48 Gbits/sec
[  4]   4.00-5.00   sec   644 MBytes  5.40 Gbits/sec
[  4]   5.00-6.00   sec   658 MBytes  5.52 Gbits/sec
[  4]   6.00-7.00   sec   641 MBytes  5.38 Gbits/sec
[  4]   7.00-8.00   sec   639 MBytes  5.36 Gbits/sec
[  4]   8.00-9.00   sec   644 MBytes  5.41 Gbits/sec
[  4]   9.00-10.00  sec   611 MBytes  5.13 Gbits/sec
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bandwidth
[  4]   0.00-10.00  sec  6.19 GBytes  5.32 Gbits/sec                  sender
[  4]   0.00-10.00  sec  6.19 GBytes  5.32 Gbits/sec                  receiver

5Gbps超えです。 少し速度が低いのはMTUが低いからとかそんなところだと思いますが、ルータとしては十分な速度でしょう。 謎の中華ボードですが、しっかり速度が出ています。 うれしいですね。

まとめ

今回は謎の高性能中華ボードOK1046A-C2を紹介しました。

ルータを組んだとは言っても、まだ本運用には至っていません。 これからOpenWrtへの理解を深めて、現在運用しているRTX1200を置き換えることを目標にしたいと思います。

この記事を読んでOK1046A-C2に興味がわいてきた方がいれば、ぜひ私と一緒に人柱になりませんか?


  1. 言うほど格闘してない

  2. 最初はただのext4イメージファイルなのかと思ったのですが、どうやら違うようでした。このext4イメージの作り方がわかる方がいらっしゃれば、教えていただければ幸いです。

発言内容を自動で文字起こしするDiscord botを作った話

こんにちは。 学生なので夏休みに入りました。 皆様いかがお過ごしでしょうか。

今回はDiscordの発言内容を自動で文字起こしするDiscord botを書いたので、それについて技術的にはまった点などを備忘録的に書いていきたいと思います。

そもそも何故そんな需要があったかというと、今の私のインターン先でチームで開発をしているのですが、その会議を文字起こししたいという話があったからでした。

今回のbotはNode.js + TypeScriptで記述しました。 普段の私であれば使用言語にPythonを選択するのですが、discord.pyよりもdiscord.jsの方が音声受信周りが充実していたのと、TypeScriptの方が型周りの開発体験が良かった1ので、TypeScriptを書くことにしました。

実際に作ったものはこちらです。

github.com

では詳細を見ていきましょう。

Discordの音声を受信してWAVファイルにする

Discordの音声受信はユーザごとに音声を受信します。

今回はユーザが一通りしゃべり終わったタイミングでしゃべった内容を文字起こしにかけ、リアルタイムにその内容をテキストチャンネルに投稿したいので、VoiceConnectionspeakingイベントを監視して、speakingイベント発火時に録音を開始、createStreamの結果得られたReadableStreamがendの時に取得した録音データを文字起こしにかけ、その結果を発言ユーザの名前と共に開始コマンドが入力されたテキストチャンネルに投稿する、という処理を行っています。

詳しくはこのあたりを参照していただければわかるかと思います。

Google Cloud Speech-to-Text APIを使って文字起こし

文字起こしには、精度がそれなりによいGoogle Cloud Speech-to-Textを利用しました。 と書きましたが、AWSのTranscribeも結構良さげですね。 今まさに悩んでいます2

まぁとりあえずGoogle Cloud Speech-to-Textで組んだのでその辺の説明をしようと思います。

Google Cloud Speech-to-Textでは、いくつかのファイル形式に対応していますが、WAVが一番扱いやすいと思います。 discord.js側が用意してくれるのは32bitステレオなWAVファイルですが(これについては後述)、Google Cloud Speech-to-Textに渡すためには16bitモノラルなWAVファイルを用意してあげる必要があり、fluent-ffmpegというパッケージを利用してNode.js側からffmpegを叩いて変換をしました。

詳細はsrc/speech.tsを参照してください。

遭遇した問題

ここからは実際に遭遇して、解決が難しかった問題について書いていきます。

discord.jsが実際に吐き出すWAVファイルがガイドの形式と異なる

このことはどのドキュメント、Webサイトにも載っていなかったので確かではないのですが、このガイドに「Signed 16-bit PCM」と書いてあるのに対し、手元で実行した結果得られたファイルをAudacityにかけてみると、32bitにしたところ正常に再生されました。 何らかの仕様が変わってそうなった可能性が高そうな気がしますが、これはどこが悪いのかわからない(そもそもDiscord側からはopus形式で来ていて、それを手元でWAVに変換しているっぽい)ので、何とも言えません。 とにかく、fluent-ffmpegの読み込み形式を s32le にしたところ正常に音声認識されるようになったので、そのようにしました。

一定時間が経過するとVoiceConnectionのspeakingイベントが発火しなくなる現象への対処

このこともどのドキュメント、Webサイトにも載っていなかったので確かではないのですが、VoiceConnectionを張って一定時間経過すると、speakingイベントが発火しなくなる現象がこちらの手元で起きました。 これへの対処としては、一定時間毎に無音をplayしてやることで回避することができました。

これらの問題は二つともなんか腑に落ちませんが、まぁこの対処で動いているので良しとしました。 動くことこそ正義3

おわりに

今回は簡単にですが、自動で発言内容を文字起こしするDiscord botについて説明しました。

Google Cloud Speech-to-TextやAmazon Transcribeの精度は実用には厳しいですが、非常に使いやすいものにはなってきているかと思います。 今回はDiscord botとして実装しましたが、ほかにも活用法を検討中です。 文字起こしを使ってこういうサービスが欲しい!などあればぜひお話をお聞かせください。


  1. Pythonの漸進的型付け界隈はもうちょっと頑張ってほしいという気持ちがあります。いや私はその辺の研究をしているので、お前が頑張れという話なのですが……

  2. 私は基本的に業務でも趣味でもAWSを使っているので、GCPを使うよりはAWSを使った方が楽といった事情があります

  3. そうかな?

inkwellを使ってRustでLLVMをやっていく

こんにちは。 そろそろ夏ですね。

今回の記事はRustでLLVMをやっていくことができるクレート、inkwellについてです。 inkwellを紹介している記事自体はあるのですが、実際にinkwellを利用して何かを作ってる記事が少なかったのでこの記事を書きました。

LLVM自体はそれなりに情報はあり、またきつねさん本などもあることから、トラブルシューティングはしやすいほうだと思います。 とは言いつつLLVMに対しては触りながら知見をためていったこともあり、様々の理解に少し時間がかかってしまいました。 この記事や私の書いたソースコードが少しでもRustでLLVMをやろうとする人の役に立てば幸いです。

inkwellを利用して実際に作ったものはこちらです。

github.com

小さな独自のLISP方言をLLVM IRにコンパイルするプログラムです。 まだ全然関数などがそろっていないため実用には程遠いです。

inkwell

inkwellの公式ドキュメントはこちらです。

inkwell自体はllvm-sysを安全に(unsafeで囲う必要がなく)、Rust風のAPIで使えるようにしたものです。

導入

LLVMの導入

まずはLLVM自体を導入してあげる必要があります。 LLVMのバージョンを指定しての導入にはllvmenv1がおすすめです2

依存ビルドツールにcmake、make、ninja、g++/clang++があるので、これらをインストールします。 あとは次のコマンドで導入できます。

$ cargo install llvmenv
$ llvmenv init
$ llvmenv build-entry 12.0.0

inkwellの導入

inkwellを導入は、Cargo.tomlのdependenciesに以下を記述してあげるだけです。

inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "master", features = ["llvm12-0"] }

featuresは利用したいLLVMのバージョンに合わせて変更します。詳しくは inkwellのREADME を見てください。

inkwellを使うためには、環境変数llvm-sysにLLVMの場所を教えなくてはいけません。 fishでは次のようにして場所を指定します(各々のシェルに応じてコマンドは変えてください)。

$ set -x LLVM_SYS_120_PREFIX (llvmenv prefix)

これでinkwellの導入まで完了です。

実際に動かしてみる

では実際にinkwellを用いてLLVMのプログラムをビルドし動かしてみましょう。

use inkwell::context::Context;
use inkwell::OptimizationLevel;

fn main() {
    let context = Context::create();
    // moduleを作成
    let module = context.create_module("main");
    // builderを作成
    let builder = context.create_builder();

    // 型関係の変数
    let i32_type = context.i32_type();
    let i8_type = context.i8_type();
    let i8_ptr_type = i8_type.ptr_type(inkwell::AddressSpace::Generic);

    // printf関数を宣言
    let printf_fn_type = i32_type.fn_type(&[i8_ptr_type.into()], true);
    let printf_function = module.add_function("printf", printf_fn_type, None);

    // main関数を宣言
    let main_fn_type = i32_type.fn_type(&[], false);
    let main_function = module.add_function("main", main_fn_type, None);

    // main関数にBasic Blockを追加
    let entry_basic_block = context.append_basic_block(main_function, "entry");
    // builderのpositionをentry Basic Blockに設定
    builder.position_at_end(entry_basic_block);

    // ここからmain関数に命令をビルドしていく
    // globalに文字列を宣言
    let hw_string_ptr = builder.build_global_string_ptr("Hello, world!", "hw");
    // printfをcall
    builder.build_call(printf_function, &[hw_string_ptr.as_pointer_value().into()], "call");
    // main関数は0を返す
    builder.build_return(Some(&i32_type.const_int(0, false)));

    // JIT実行エンジンを作成し、main関数を実行
    let execution_engine = module.create_jit_execution_engine(OptimizationLevel::Aggressive).unwrap();
    unsafe {
        execution_engine.get_function::<unsafe extern "C" fn()>("main").unwrap().call();
    }
}

このプログラムは、LLVMでmain関数を宣言し、main関数内でprintf関数を呼び出すコードを実行しています。 実行すると、Hello, world!と表示されるはずです。

inkwellを使えば、Rustからこれだけ簡単にLLVMをいじることができます。 LLVMの他の命令を使うには、Builderのメソッドを参照するとよいでしょう。 ドキュメント自体はそんなに充実しているとは言えませんが、命令をビルドするメソッドの引数と返り値の型がわかれば後は組み立てていけばいいので、慣れてくればそんなに迷うことはなくなるはずです。

おわりに

今回はRustで簡単にLLVMを扱うクレート、inkwellを紹介し、その導入と命令の記述についてサンプルコードとともに簡単に説明しました。 初めてinkwellやLLVMに触る人は、とりあえずは上のサンプルコードに様々な命令を追加してみて、inkwellとLLVMがなんとなくわかってきたら実際の言語作成に取り掛かってみるのが良いのではないでしょうか。

是非皆さんもRustでオレオレコンパイラフロントエンドを作っていきましょう3


  1. こちらです。メンテナ不足で悩んでいるようですが……

  2. aptが使える環境をご利用の方はこっちのほうが楽でいいかもしれません

  3. 次回はプログラミング言語自作に入門する記事が書きたいですね……