現代のデジタル環境では、基本的な機能を備えたシステムを構築するだけでは、より高度なアプリケーション要件を満たすことができなくなりました。高負荷環境下でも安定して効率的に拡張できるシステムを開発する必要があります。
多くの開発者やアーキテクトの実践により、システムのスケーラビリティの向上には特有の課題が伴うことが多いことが証明されています。小さな効率の問題でも、数百万倍に増幅された負荷の下ではシステムが機能不全に陥る可能性があります。では、どのような負荷がかかってもアプリケーションが迅速に応答できるようにするにはどうすればよいでしょうか?
この記事では、スケーラブルなシステムを構築する際のパフォーマンス最適化戦略について詳しく説明します。フロントエンドかバックエンドか、使用されるプログラミング言語に関係なく、あらゆる種類のコード ベースに適用される一般的な戦略をいくつか見ていきます。これらの戦略は単なる理論上のものではなく、世界中の最も困難なテクノロジー環境のいくつかで実際に適用され、実証されています。 Facebook チームのメンバーとして、私は個人的に、Facebook の軽量広告作成エクスペリエンスや Meta Commerce Suite などの複数のプロジェクトにこれらの最適化手法を適用することに携わっています。
したがって、次の大きなソーシャル ネットワークを構築している場合でも、エンタープライズ グレードのソフトウェア スイートを構築している場合でも、あるいは単に個人プロジェクトを最適化したい場合でも、ここで説明する戦略はツールボックスの貴重な資産となるでしょう。さあ、探索を始めましょう。
先読み
プリフェッチは、ユーザーの行動の予測に基づいたパフォーマンス最適化手法です。ユーザーがアプリケーションを操作していると、システムはユーザーの次のアクションを予測し、関連データを事前に取得できると想像してください。このアプローチにより、シームレスなエクスペリエンスが実現されます。データが必要なときに、ほぼ即座に利用できるため、アプリケーションがより高速で応答性が高いように見えます。必要が生じる前にプロアクティブにデータを取得すると、ユーザー エクスペリエンスが大幅に向上しますが、過度に使用すると、帯域幅、メモリ、さらには処理能力などのリソースの浪費につながる可能性があります。 Facebook は、友達の提案機能など、機械学習に依存する複雑な操作でプリフェッチを多用しています。
プリフェッチはいつ行われますか?
プリフェッチでは、ユーザーが明示的に必要性を表明する前に、データを取得するリクエストをサーバーに積極的に送信します。これは魅力的に見えるかもしれませんが、開発者は効率とリソース使用量の間のバランスを確保する必要があります。
A. サーバーの応答時間を最適化する (バックエンド コードの最適化)
プリフェッチを実装する前に、まずサーバーの応答時間が最適化されていることを確認してください。バックエンド コードの最適化により、次の方法でサーバーの応答時間を短縮できます。
- データベース クエリを合理化し、取得時間を短縮します。
- 複雑な操作を同時に実行できるようにします。
- 繰り返しの API 呼び出しを減らし、同じデータを繰り返し取得することを避けます。
- サーバーの応答が遅くならないように、不必要な計算を排除します。
B. ユーザーの意図を確認する
プリフェッチの中核は、ユーザーの次のアクションの予測です。ただし、予測が不正確になる場合もあります。ユーザーが一度もアクセスしたことのないページや機能のデータをシステムがプリフェッチすると、リソースが無駄になります。したがって、開発者は、ユーザーの行動パターンの追跡やユーザーの積極的な関与のチェックなど、ユーザーの意図を評価するメカニズムを実装して、使用される可能性が高い場合にのみデータが収集されるようにする必要があります。
プリフェッチの実装方法
プリフェッチは、任意のプログラミング言語またはフレームワークで実装できます。 React を例として、プリフェッチの実装方法を示します。
単純な React コンポーネントを考えてみましょう。コンポーネントのレンダリングが完了すると、AJAX 呼び出しをトリガーしてデータをプリフェッチします。ユーザーがこのコンポーネントのボタンをクリックすると、2 番目のコンポーネントはこのプリフェッチされたデータを使用します。
import React, { useState, useEffect } from 'react';
import axios from 'axios';
function PrefetchComponent() {
const [data, setData] = useState(null);
const [showSecondComponent, setShowSecondComponent] = useState(false);
// 组件渲染完成后立即预取数据
useEffect(() => {
axios.get('https://api.example.com/data-to-prefetch')
.then(response => {
setData(response.data);
});
}, []);
return (
<div>
<button onClick={() => setShowSecondComponent(true)}>
Show Next Component
</button>
{showSecondComponent && <SecondComponent data={data} />}
</div>
);
}
function SecondComponent({ data }) {
// 在这个组件中使用预取的数据
return (
<div>
{data ? <div>Here is the prefetched data: {data}</div> : <div>Loading...</div>}
</div>
);
}
export default PrefetchComponent;
上記のコード例では、PrefetchComponent コンポーネントはレンダリング直後にデータをフェッチします。ユーザーがボタンをクリックすると、以前に取得したデータを使用して SecondComponent コンポーネントが表示されます。
メモ化
コンピューター サイエンスでは、「同じことを繰り返さない」という原則が、優れたコーディング習慣の中心です。この原則はパフォーマンス最適化の効果的な手段でもあり、メモ化テクノロジの基礎となっています。メモ化は、特定の操作を繰り返し実行すると、特に操作の結果が頻繁に変更されない場合に、大量のリソースを消費する可能性があるという考えに基づいて構築されています。では、なぜすでに行われた作業を繰り返すのでしょうか?
メモ化は計算結果をキャッシュすることでアプリケーションのパフォーマンスを向上させます。同じ計算が再度要求されると、システムはまず結果がすでにキャッシュにあるかどうかを確認します。キャッシュされている場合、結果はキャッシュから直接取得されるため、実際の計算ステップが省略されます。基本的に、メモ化には以前の結果の保存が含まれます (したがって、この名前が付けられています)。これは、計算コストが高く、同じ入力で頻繁に呼び出される関数の場合に特に効果的です。それは、学生が複雑な数学の問題を解いて、その答えを本の余白にメモするようなものです。将来の試験で同じ問題が出た場合、生徒は問題を再度解く必要がなく、本の横にあるメモを見直すだけで済みます。
メモ化をいつ使用するか?
メモ化はあらゆる状況で機能するわけではありません。一部のシナリオでは、メモ化によりメモリ消費量が増加する可能性があります。したがって、この手法をいつ使用するかを正しく特定することが重要です。
- データの変更がまれな場合: 入力が一貫している場合に一貫した結果を返す関数、特に計算集約型の関数の場合、メモ化は理想的な選択肢です。これにより、後続の同一の呼び出しで計算量が無駄にならないことが保証されます。
- データの機密性がそれほど高くない場合: メモ化の使用を検討する場合、セキュリティとプライバシーの問題も無視できない重要な要素です。すべてをキャッシュするのは魅力的かもしれませんが、必ずしも安全であるとは限りません。たとえば、支払い情報、パスワード、その他の個人情報などのデータは決してキャッシュすべきではありません。ただし、ソーシャル メディアの投稿に対する「いいね!」やコメントなどの無害なデータは、パフォーマンスを向上させるために安全にメモ化できます。
メモ化の実装方法
React では、useCallback や useMemo などのフックを使用して記憶を実装できます。簡単な例を見てみましょう。
import React, { useState, useCallback, useMemo } from 'react';
function ExpensiveOperationComponent() {
const [input, setInput] = useState(0);
const [count, setCount] = useState(0);
// 模拟一个计算开销很大的操作
const expensiveOperation = useCallback((num) => {
console.log('Computing...');
// 模拟耗时长的计算
for(let i = 0; i < 1000000000; i++) {}
return num * num;
}, []);
const memoizedResult = useMemo(() => expensiveOperation(input), [input, expensiveOperation]);
return (
<div>
<input value={input} onChange={e => setInput(e.target.value)} />
<p>Result of Expensive Operation: {memoizedResult}</p>
<button onClick={() => setCount(count + 1)}>Re-render component</button>
<p>Component re-render count: {count}</p>
</div>
);
}
export default ExpensiveOperationComponent;
この例では、pricesOperation 関数は、計算負荷の高いタスクをシミュレートします。 useCallback フックを使用して、コンポーネントがレンダリングされるたびにこの関数が再定義されないようにします。さらに、 useMemo フックは、コンポーネントが再レンダリングされても、入力が変更されていなければ計算が繰り返されないように、pricesOperation の結果を保存するために使用されます。
並行取得
並列データ取得とは、一度に 1 つずつではなく、複数のデータ セットを同時に取得することを指します。これは、スーパーマーケットのレジで 1 人ではなく複数のレジ係が対応するようなものです。顧客への対応が速くなり、待ち時間が短縮され、全体的な効率が向上します。データ処理の分野では、多くのデータ セットが互いに無関係であることを考慮すると、特に複雑なデータの取得に時間がかかるシナリオでは、並列取得によりページの読み込みを大幅に高速化できます。
並列フェッチをいつ使用するか?
- 各データセットが独立しており、取得処理が複雑な場合: 取得するデータセット間に依存関係がなく、各データセットの取得に時間がかかる場合は、この並列取得により、処理速度が効果的に向上します。
- バックエンドは広く使用されているため、フロントエンドを使用する場合は注意してください: ただし、並列取得はサーバーの応答を改善することでバックエンドに大きな影響を与える可能性があります。速度が速いため、フロントエンドで使用する場合は特に注意する必要があります。並列リクエストが多すぎると、クライアントの負荷が増加し、ユーザー エクスペリエンスに影響を与える可能性があります。
- ネットワーク リクエストの優先順位付け: データ取得に複数のネットワーク リクエストが含まれる場合、ベスト プラクティスは、1 つのメイン リクエストに優先順位を付けてフロントエンドに表示し、同時に他のリクエストをフロントエンドで並行して処理することです。背景。これにより、最も重要なデータが最初にフェッチされ、他の重要性の低いデータがバックグラウンドで並行してロードされるようになります。
並列フェッチの使用方法
PHP では、最新の拡張機能やツールの開発により、並列処理が容易になりました。以下は、同時実行 {} コード ブロックを使用した基本的な例です。
<?php
use Concurrent\TaskScheduler;
require 'vendor/autoload.php';
// 假设这些是一些从各种来源获取数据的函数
function fetchDataA() {
// 模拟延迟
sleep(2);
return "Data A";
}
function fetchDataB() {
// 模拟延迟
sleep(3);
return "Data B";
}
$scheduler = new TaskScheduler();
$result = concurrent {
"a" => fetchDataA(),
"b" => fetchDataB(),
};
echo $result["a"]; // Outputs: Data A
echo $result["b"]; // Outputs: Data B
?>
この例では、fetchDataA と fetchDataB は 2 つのデータ取得関数を表します。同時実行 {} コード ブロックを使用すると、これら 2 つの関数を同時に実行できるため、両方のデータ セットを取得するのに必要な合計時間が短縮されます。
遅延読み込み
遅延読み込みは、本当に必要なときにのみデータやリソースを読み込むという基本的な考え方を持つ設計パターンです。すべてを事前にロードするのとは異なり、遅延ロードでは、最初のビューに必要なコンテンツのみをロードし、必要に応じて追加のリソースをロードします。これは、すべての料理を事前に準備するのではなく、顧客が特定の料理を注文したときにのみ調理を開始するレストランに似ています。たとえば、Web ページでは、ユーザーがボタンをクリックしてモーダル ボックスを開いたときにのみモーダル ボックスのデータがロードされます。このようにして、実際に必要になる瞬間までデータの取得を延期できます。
遅延読み込みを実装する方法
遅延読み込みを効果的に実装するための鍵は、データ取得プロセス中にユーザーに明確なフィードバックを提供して、ユーザー エクスペリエンスを最適化することです。一般的な方法は、データがまだ利用可能でない場合でも、ユーザーがリクエストが処理されていることを理解できるように、データの取得中に回転読み込みアニメーションを表示することです。
React での遅延読み込みの例
React コンポーネントに遅延読み込みを実装する例を次に示します。このコンポーネントは、ユーザーがボタンをクリックしてモーダル コンテンツを表示したときにのみデータを取得します。
import React, { useState } from 'react';
function LazyLoadedModal() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const fetchDataForModal = async () => {
setIsLoading(true);
// 模拟一次 AJAX 获取数据的调用
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
setIsLoading(false);
setIsModalOpen(true);
};
return (
<div>
<button onClick={fetchDataForModal}>
Open Modal
</button>
{isModalOpen && (
<div className="modal">
{isLoading ? (
<p>Loading...</p> // 这里可以使用旋转圈或加载动画
) : (
<p>{data}</p>
)}
</div>
)}
</div>
);
}
export default LazyLoadedModal;
この例では、ユーザーが「モーダルボックスを開く」ボタンをクリックした場合にのみ、モーダルボックスデータの取得が開始されます。それまでは、不要なネットワーク要求は開始されません。データのフェッチが開始されると、ユーザー要求が処理中であることを示すロード メッセージ (またはスピナー) が表示されます。
結論は
今日の高速デジタル時代では、ミリ秒単位の応答時間が重要です。ユーザーは素早い応答を求めており、企業にはユーザーを待たせるわけにはいきません。高品質のデジタル エクスペリエンスを提供するには、単なる最適化ではなく、パフォーマンスの最適化が必要不可欠になっています。
プリフェッチ、メモ化、並列フェッチ、遅延読み込みなどのテクノロジを通じて、開発者はアプリケーションのパフォーマンスを効果的に向上させることができます。これらの戦略はアプリケーションとアプローチが異なりますが、アプリケーションを可能な限り効率的かつ迅速に実行するという目標は共通しています。
重要な点は、万能の解決策や「特効薬」はないということです。すべてのアプリケーションはユニークであり、パフォーマンスの最適化には、アプリケーションのニーズの深い理解、ユーザーの期待の認識、適切な技術の効果的な適用を組み合わせる必要があります。これは継続的な改善と学習のプロセスです。