Dropbox が Capture 用にカスタム Rust ライブラリを構築した理由

Dropbox Capture blog

Dropbox Capture は、画面録画、動画メッセージ、スクリーンショット、または GIF を使用した動画をチームが非同期で簡単に共有できる、新しいビジュアルコミュニケーションツールです。正式なオンボーディングは不要で、すぐにアイデアを共有し始めることができます。Capture の操作性の鍵となっているのがシンプルさです。この価値は、Capture の基盤として開発されたコードにも反映されています。

私たちのチームには、「マルゲリータピザのように」という指針があります。マルゲリータピザがシンプルさの点で完璧なように(トマトソース、モッツァレラ チーズ、バジルの他には何も必要ありません)、Capture のコンポーネントもできるだけシンプルでわかりやすくなるよう試みました。早い段階から、Electron と Node を使えば、クロス プラットフォームの TypeScript アプリを macOS と Windows の両方に向けて構築するのは簡単だということはわかっていました。しかし、ネイティブの OS レベルのコードをすばやく、かつ信頼性の高い方法でシンプルに呼び出すことができる適切な 3 つ目のコンポーネントを見つけるには、もう少し実験が必要となりました。

理想としていたのは、スムーズに、一貫性のある方法で、複数のプラットフォームを対象とし、私たちのチームの開発者が簡単に構築できる効率化されたコードベースです。また、スクリーンキャプチャと画面録画の機能性を強化し、エラー処理の精度を高め、バックグラウンドではパフォーマンスを高速化したいとも考えていました。 それだけではなく、各レイヤーでの制御を強化する仕組み、つまり、多くの複雑な手順がなくてもネイティブコードを呼び出せる仕組みも求めていました。この仕組みは、私たちが構築を目指していた新機能を、より効果的にサポートする必要もありました。

こうした問題を解決するには、もっと TypeScript を活用する、C++ を使うなど、さまざまな方法があったと考えられます。しかし、私たちは、Rust を使用するという結論に至りました。
ある意味、結論を出すのは簡単でした。というのも、Dropbox には、Rust を Dropbox 製品に組み込む開発者によって構成される活発なコミュニティがあるからです。少し前に、デスクトップ クライアントの心臓部である同期エンジンは Rust を使って書き直されました。Dropbox フォルダがパソコン上ですばらしい働きをするのは、このエンジンのおかげです。私たちは、このエンジンをファイル圧縮クラッシュをレポートするインフラストラクチャで使用し、さらには、エクサバイト級のカスタム ストレージインフラストラクチャである Magic Pocket ではファイルデータのストレージの最適化に使用しています。

また、Rust が私たちのニーズに最適であることもわかりました。カスタム Rust ライブラリを構築することで、720p から 4K までの高品質の画面録画が可能となり、スクリーンショットと画面録画のすばやい共有、エラー処理機能の劇的な改善が実現され、信頼性の高い操作性をユーザーに提供できるようになりました。

今回の取り組みは、近年、最も愛されているプログラミング言語を学ぶ格好の口実になったとも考えています。

ハックウィーク(Hack Week) から現在まで

Capture は、迅速なイテレーションが重要となる社内のハック ウィーク *1 プロジェクトとして開発が始まりました。

*1 ハックウィーク:Dropboxの全社員が年に一度の 7 日間、本業から離れて突き抜けたアイデアやリスクの高いアイデアを追求することができる週です。2011 年以来毎年行われています。この期間にお客様や会社に変化をもたらすプロジェクトから、自分自身の成長に影響を与えるプロジェクトまで、あらゆる面でのイノベーションが行われています。

初期のバージョンでは、少数のサードパーティライブラリを使用して、スクリーンショットの取得や GIF の処理などの処理を実行しました。既存の小さなコードを寄せ集めることで、すばやくプロトタイプを開発し、最初の仮説の検証と新機能による実験をスムーズに進めることができました。しかし、Capture のコードベースの長期的な健全性と、これらのサードパーティライブラリが引き起こす複雑性を考慮すると、初期に発生したこの技術的負債をいつかは解消する必要があると考えていました。

私たちが使用していたサードパーティライブラリは主に、シェルベースのアプリケーションでした。Capture は、それぞれのシェルアプリケーションにコマンドを送信し、標準エラー出力(stderr)や標準出力(stdout)を応答として受け取ります。つまり、特定のタスクを完了したいときには、必ずアプリケーションを起動させることになります。場合によっては、アプリケーションを継続的に実行しておき、入力を待機させることになります。理想的な方法ではありません。

さらに重要なことに、これは、Capture がネイティブのコードと通信する方法に、もともと脆弱性があることも意味していました。シェルアプリケーションからの出力行は、その都度パースする必要がありました。パースに失敗した場合は、エラーであると見なしました。それが実際にエラーだった場合、その問題はマスクされている可能性が高く、ネイティブコード内でどこに問題があるのかを突き止めることができませんでした。当然ながら、この挙動ではモニタリングやエラー処理をするのが困難です。

このライブラリは他にも、開発の観点から別の問題がありました。macOS と Windows では、たとえ同じクロスプラットフォームのライブラリ内であっても、API が大きく異なることがあり、そのことが双方のプラットフォーム向けの開発を一段と複雑にしたのです。ライブラリによっては、適切にメンテナンスされている一方で、必要な機能が不足しているものもありました。逆に、必要な機能がすべて揃っていても、適切にメンテナンスされていないライブラリもありました。それぞれにトレードオフの問題がつきまといましたが、簡単に解決できるものと、そうでないものとがありました。

たとえば、個々のライブラリに変更を加えたい場合は、それぞれをビルドしてから Capture に組み込む方法について組織的な知識が必要となりました。ここでの問題点は、パースに関するたった 1 つのバグを修正するために、1 人のエンジニアが何時間もの貴重な開発時間を割いて、Windows の画面録画ライブラリのビルド方法を学ぶ必要があったことです。

救世主となった Rust

Rust は、 Capture チームにとって新しい開発言語だったため、当初、チームは非常に小さな成果を積み上げながら、開発を進めていきました。最初に私たちが焦点を当てたのは、シンプルな関数を書き直すことです。これらを書き直さなければ、サードパーティライブラリを使用する必要がありました。たとえば、activate-windows は、ウィンドウを前面に表示し、そのウィンドウのみを録画するライブラリですが、以前は macOS のみに対応していました。私たちは、すぐに macOS 上でこの機能を Rust に移植し、その後でこの機能を使用できなかった Windows に移植できました。

こうした初期の成功によって、私たちは、もっと野心的なことを試そうという自信を持つようになったのです。学びが深まるにつれて、カスタム Rust ライブラリに移植した機能は増え、Capture に以下のメリットをもたらしました。

オーバーヘッドの排除:オーバーヘッドを発生させずに(また、信頼性の高い方法で)Neon バインディングを使用して、TypeScript から簡単にネイティブの OS コードを呼び出すことができました。つまり、特定のタスクを完了するために個別のシェル アプリケーションを起動する必要がなくなったのです。たとえば、スクリーンショットの取得は、直ちに実行できるようになり、高速化されました。以前、この処理は非同期で、シェルアプリケーションからの応答を待機しなければなりませんでした。

エラー処理の改善:Rust によって、エラー処理機能も劇的に改善しました。ほとんどの Capture コードが私たち独自のライブラリ内で実行されるようになり、macOS と Windows の両方で一貫した API を使用することで、より強力なログとモニタリングを追加することができました。もう、シェルアプリケーションからの出力をパースする必要はなくなったのです。すべてのコードを 1 か所に集めたので、アプリの実際の挙動について、より多くの洞察を得ることができます。

管理の強化:私たち自身のライブラリを手に入れたので、修正と拡張にかかる時間がさらに短縮されました。すべてのコードを 1 か所に集めたことで、不明瞭な問題への対処も簡単になりました。たとえば、キャプチャの取得や 3 画面を超える録画を行うときに、頻繁に不安定になっていましたが、その問題に対処できるようになりました。結果として、プラットフォーム間のビルドパイプラインもシンプルになりました。

フットプリントの削減:サードパーティ ライブラリをインクルードする必要がないため、アプリケーション全体のサイズも削減されました。たとえば、Rust で上述の機能を書き直した結果、macOS で約 17 MB の Swift ライブラリが不要になりました。バックグラウンドでシェルアプリケーションを常時実行する代わりに必要な関数を呼び出すだけで済むようになったため、必要なメモリも以前よりも少なくなりました。

新機能:前述した activate-windows の例のように、Rust への移植によって、macOS でのみ使用できた機能を Windows に移植するなど、以前には不可能だったことが可能になりました。その他にも、新しいトリミング ツールと録画制御機能の導入、「音声のみ」または「カメラのみ」などの新しい録画の種類の追加、720p、1080p および4K への録画品質の向上も実現しました。別の言語でこうした機能を構築できなかったというよりも、Rust のおかげで別の言語よりも短期間で、かつ少ない労力で構築できたといえます。

Capture と Rust の今後

Capture の開発では、いくつか驚いたことがありますが、その 1 つが Rust を使うと、とても簡単に開発を始められるということです。Rust コードを本番環境にリリースするまでは、数週間しかかかりませんでした。また、Rust に精通したエンジニアが集まる、協力的な Dropbox コミュニティから多くの支援が得られました。私たちが行き詰まったときに、コミュニティのエンジニアの皆さんがサポートや助言をしてくれました。また、私たちのチームが成長するにつれて、新しい開発者も、少ないトレーニングで開発を進めていけるようになると思います。現在は、以前のように複数のライブラリの処理に苦労することはありません。とてもシンプルなドキュメントを渡して、「ここを見れば、1 つのコマンドですべてをビルドする方法がわかりますよ」と言うだけでよくなりました。Rust のおかげで、開発者は、先が予測できるようになりました。私たちが大好きなマルゲリータ ピザのようにシンプルだからです。

今後、自社で開発したこのライブラリに、さらに多くの機能を移植していく予定です。すでに、Rust によって macOS 版スクリーン レコーダーの書き直しを終了しており、Windows 版でも同様な書き直しを進めています。また、GIF の作成や他の OS レベルの統合などの機能も Rust ライブラリに移植しています。私たちが特に期待しているのは、Windows 上の Rust の今後の進展です。こちらは、最近 v0.21.0 がリリースされています。

ただし、私たちは 1 つの方法に頼り切ることはしません。必要に応じて古いシェル プロセスのアプローチでサードパーティライブラリを引き続き呼び出せるように、すでに Rust を構成しました。つまり、計画的に、どの機能を、いつ書き直すかを選択することができます。もちろん、Rust での処理が困難な場合は、簡単に立ち戻ることもできます。ですが、Rust がもたらしてくれたメリットを考えると、私たちが情熱を持って夢中で取り組んできたことは、正しかったといえます。

Capture は現在ベータ版です。まだお試しいただいていなければ、ぜひこちらからお試しください。

  • 0
  • 0
  • 0
  • 0