Misskey の一元化された Note 取得フローと重複防止メカニズム#
概要#
Misskey は、一元化されたバックエンドサービスとインテリジェントなフロントエンドメカニズムを組み合わせた高度な Note 取得アーキテクチャを実装しています。このアーキテクチャは、重複取得の防止、障害の適切な処理、UI の応答性維持、そしてストリーミング配信時の Note 可視性制御を実現します。
セキュリティに関する注記: このドキュメントで説明されているアーキテクチャの一部は、CVE-2026-28431 および GHSA-r33c-qg3g-v9cr で報告された Note 可視性とストリーミングに関するセキュリティ問題に対応するために強化されています。
1. 一元化された Note 取得アーキテクチャ#
1.1 バックエンド: ActivityPub Note 解決#
バックエンドはApNoteServiceをリモートの連合サーバーから Note を取得・解決するための中心的なサービスとして使用しています。3 つの主要なメソッドがコア機能を提供します:
- fetchNote(): Note がローカルに既に存在するかをチェックする単純なデータベース検索を実行
- createNote(): ActivityPub データから新しい Note を作成し、組み込みの重複処理機能を持つ
- resolveNote(): 既存の Note を返すか、リモートサーバーから取得して新規作成するメインエントリーポイント
ApDbResolverService.getNoteFromApId()メソッドがデータベースクエリを処理し、ローカルの Note ID とリモート URI の両方をチェックします。
1.2 フロントエンド: Timeline と Pagination コンポーネント#
フロントエンドの Note 取得は 2 つの主要なコンポーネントを通じて一元化されています:
- MkTimeline.vue: WebSocket を介したリアルタイム Note 配信と最小化された Note の遅延取得を管理
- MkPagination.vue: 無限スクロールとキューイングを使用したページネーション付き Note 読み込みを処理
misskeyApi 関数は、すべての Note 取得操作のための Promise ベースの API ラッパーを提供します。
2. 重複取得を防止するガードロジック#
2.1 多層重複防止戦略#
Misskey は重複した Note 取得を防ぐために 4 層のアプローチを採用しています:
レイヤー 1: 分散ロック(バックエンド)#
acquireApObjectLock () 関数は、Redis ベースの分散ロックを使用して、複数のサーバーインスタンス間で同じ Note URI の並行取得を防ぎます:
const unlock = await acquireApObjectLock(this.redisForTimelines, uri);
ロックの特性:
- キーパターン:
lock:ap-object:{uri} - タイムアウト: 30 秒
- リトライ試行:最大 600 回、100ms インターバル
- 特定の Note URI に対して一度に 1 つのプロセスのみが取得を実行することを保証
このロックはApNoteService.resolveNote()や、受信 ActivityPub アクティビティを処理するApInboxService全体で取得されます。
レイヤー 2: データベース存在チェック(バックエンド)#
分散ロック内で、コードはNote が既に存在するかをチェックします:
// このサーバーに既に登録されていたらそれを返す
const exist = await this.fetchNote(uri);
if (exist) return exist;
このパターンは複数の場所に現れます:
- ApNoteService.resolveNote()
- ApInboxService (Create/Update アクティビティ用)
- ApInboxService (Announce アクティビティ用)
レイヤー 3: データベース制約処理(バックエンド)#
データベース挿入レベルで、NoteCreateService.create()は PostgreSQL の重複キーエラー(エラーコード 23505)をキャッチします:
} catch (e) {
// duplicate key error
if (isDuplicateKeyValueError(e)) {
const err = new Error('Duplicated note');
err.name = 'duplicated';
throw err;
}
レイヤー 4: 重複エラーリカバリ(バックエンド)#
ApNoteService.createNote()は競合状態を処理するエラーリカバリを実装しています:
} catch (err: any) {
if (err.name !== 'duplicated') {
throw err;
}
this.logger.info('The note is already inserted while creating itself, reading again');
const duplicate = await this.fetchNote(value);
if (!duplicate) {
throw new Error(`The note creation failed with duplication error even when there is no duplication: ${entryUri}`);
}
return duplicate;
}
重複キーエラーが発生した場合(存在チェックと挿入の間の競合状態を示す)、コードは失敗するのではなく、データベースから Note を再取得します。
2.2 フロントエンド重複防止#
保留中の取得追跡#
MkTimeline コンポーネントは保留中の Note 取得の Map を維持します:
const pendingNoteFetches = new Map<string, Promise<void>>();
const scheduleMinimizedNoteRetry = (data: { id: string }) => {
if (pendingNoteFetches.has(data.id)) return; // 重複をガード
const retryPromise = retryWithFibonacciBackoff(() => fetchNoteJson(data.id), {
maxAttempts: 3,
initialDelayMs: 100,
}).finally(() => {
pendingNoteFetches.delete(data.id);
});
pendingNoteFetches.set(data.id, retryPromise);
};
これにより、クライアント側で同じ Note ID に対する重複リトライ操作を防ぎます。
3. フィボナッチ式バックオフリトライメカニズム#
3.1 実装#
retryWithFibonacciBackoff ユーティリティは、バックオフ遅延に純粋なフィボナッチ数列を実装しています:
export async function retryWithFibonacciBackoff<T>(
task: () => Promise<T>,
options: RetryOptions,
): Promise<T> {
let nextDelayMultiplier = 1;
let followingDelayMultiplier = 2;
for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
try {
return await task();
} catch (error) {
if (attempt >= options.maxAttempts) {
throw error;
}
const delay = nextDelayMultiplier * options.initialDelayMs;
await sleep(delay);
[nextDelayMultiplier, followingDelayMultiplier] = [
followingDelayMultiplier,
nextDelayMultiplier + followingDelayMultiplier,
];
}
}
throw new Error('Retry attempts exhausted.');
}
3.2 フィボナッチ計算#
実装は分割代入を使用してフィボナッチ値を更新します:
nextDelayMultiplierが次のフィボナッチ数になるfollowingDelayMultiplierが合計(次のフィボナッチ数)になる
これにより、フィボナッチ数列に従った遅延乗数が生成されます: 1, 2, 3, 5, 8, 13, 21...
3.3 フィボナッチバックオフを使用した Note 取得#
主な使用例は失敗した Note 取得をリトライする MkTimeline.vueです:
設定:
maxAttempts: 3initialDelayMs: 100
結果として得られる遅延:
- 1 回目のリトライ: 100ms (1 × 100)
- 2 回目のリトライ: 200ms (2 × 100)
- 3 回目のリトライ: 300ms (3 × 100)
fetchNoteJson 関数は、WebSocket ストリーミングを通じて完全な可視性データなしで Note が到着したときに、HTTP を介して最小化された Note データを取得します。
3.4 フィボナッチバックオフの利点#
指数バックオフと比較して、フィボナッチバックオフは以下を提供します:
- より緩やかな進行: 指数(2^n)よりも遅い成長で、初期リトライの待機時間を削減
- 予測可能な成長: 指数よりも線形に近く、ユーザー体験が向上
- サンダリングハード防止: 過度な遅延なしにリトライ間の十分な間隔を確保
4. 非同期・ノンブロッキング取得アーキテクチャ#
4.1 Promise ベースの async/await パターン#
すべての Note 取得はmisskeyApi 関数を介した Promise ベースの async/awaitを使用しています:
export function misskeyApi<...>(endpoint, data, token?, signal?): Promise<_ResT> {
pendingApiRequestsCount.value++;
const promise = new Promise<_ResT>((resolve, reject) => {
window.fetch(`${apiUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'include',
signal,
}).then(handleResponse(resolve, reject)).catch(reject);
});
return promise;
}
MkPagination は init () と fetchMore () で async/await を使用し、データ取得中に UI がブロックされないことを保証します。
4.2 プログレッシブおよび遅延読み込み#
IntersectionObserver ベースの遅延読み込み#
MkLazy コンポーネントは IntersectionObserver を使用して、コンポーネントがビューポート付近になるまでレンダリングを延期します:
const observer = new IntersectionObserver((entries) => {
if (entries.some((entry) => entry.isIntersecting)) {
showing.value = true;
}
});
appear ディレクティブは要素が表示されたときにアクションをトリガーし、MkPagination の無限スクロール読み込みに使用されます。
CSS content-visibility 最適化#
Note は content-visibility: auto を使用して、画面外コンテンツのブラウザレンダリングを延期します:
.root {
content-visibility: auto;
contain-intrinsic-size: none auto 128px;
}
これにより、ブラウザはビューポート外の Note のレンダリング作業をスキップでき、スクロールパフォーマンスが大幅に向上します。
4.3 応答性のための UI ローディング状態#
MkPagination は個別のローディング状態を維持し、UI ブロッキングを防ぎます:
fetching: MkLoading スピナーを表示する初期ロード状態moreFetching: より多くのアイテムを読み込み中(既存コンテンツを非表示にしない)error: リトライオプション付きのエラー状態
コンポーネントはこれらの状態に基づいて条件付きレンダリングを行い、既にロードされたコンテンツとのインタラクションをブロックすることなくローディングインジケーターを表示します:
<MkLoading v-if="fetching"/>
<MkError v-else-if="error" @retry="init()"/>
<div v-else-if="empty"><!-- empty state --></div>
<div v-else>
<slot :items="items" :fetching="fetching || moreFetching"></slot>
<MkButton v-if="!moreFetching" @click="fetchMore">Load More</MkButton>
</div>
4.4 キューベースの更新管理#
クライアント側更新キュー#
MkPagination は受信更新用のキューを維持し、ユーザーがトップにいないときやタブが非アクティブなときに使用します:
const queue = ref<MisskeyEntity[]>([]);
function prepend(item: MisskeyEntity): void {
if (isHead() && !isPausingUpdate) unshiftItems([item]);
else prependQueue(item);
}
キューは条件が満たされたときに実行され、ユーザーが読んでいる間の破壊的な更新を防ぎます:
function executeQueue() {
if (queue.value.length === 0) return;
unshiftItems(queue.value);
queue.value = [];
}
Timeline コンポーネントはキュー数をボタンで表示し、ユーザーが手動で更新をトリガーできます。
バックグラウンドタブ一時停止メカニズム#
MkPagination は一時停止メカニズムを実装し、非表示タブで 10 秒後に更新を停止します:
watch(visibility, () => {
if (visibility.value === 'hidden') {
timerForSetPause = window.setTimeout(() => {
isPausingUpdate = true;
}, BACKGROUND_PAUSE_WAIT_SEC * 1000);
}
});
これにより、ユーザーがタブを切り替えたときの不要な処理とネットワークアクティビティを削減します。
バッチ Note 取得#
MkTimeline はバッチ処理を実装し、タブが非表示の間に受信した最小化された Note を処理します:
async function loadUnloadedNotes() {
const items = [...notVisibleNoteData];
notVisibleNoteData.length = 0;
const notes = await Promise.allSettled(items.map(fulfillNoteData));
const fulfilledNotes = notes
.filter((i): i is PromiseFulfilledResult<object> =>
i.status === 'fulfilled' && i.value != null)
.map(i => i.value);
}
失敗した Note 取得は、前述のフィボナッチバックオフメカニズムを使用して自動的にリトライされます。
4.5 WebSocket ベースのリアルタイムストリーミング#
MkTimeline は WebSocket 接続を確立し、Note が作成されるとそれらが非同期にプッシュされます。ストリーミング接続は最小化された Note データ(ID と基本的なメタデータのみ)を送信し、完全な Note データは必要なときにのみ遅延取得されます。
ストリーミングイベントのメタデータ拡張#
GlobalEventService.publishNoteStream()は、Note ストリーミングイベントに可視性フィルタリングのための追加メタデータを含めるように拡張されています:
public publishNoteStream(note: MiNote, type: K, value?: NoteEventTypes[K]): void {
this.publish(`noteStream:${note.id}`, type, {
id: note.id,
userId: note.userId,
visibility: note.visibility,
visibleUserIds: note.visibleUserIds,
mentions: note.mentions,
replyUserId: note.replyUserId,
body: value,
});
}
これにより、以下が実現されます:
- 早期フィルタリング: 追加のデータベースクエリなしでクライアント側で可視性判定が可能
- 帯域幅最適化: 不適切な Note をクライアントに送信する前にフィルタリング
- コンテキスト保持: リプライとメンションの関係を効率的に追跡
- 処理オーバーヘッドの最小化: Note データのプログレッシブエンハンスメント
- 高トラフィック期間中の UI 応答性の維持
5. ストリーミング配信時の Note 可視性制御#
5.1 NoteEntityService のリファクタリング#
NoteEntityServiceは、Note 可視性判定ロジックをよりモジュール化され再利用可能な形にリファクタリングしました。
可視性判定メソッドの分離#
以前はhideNote()メソッドが判定と変更の両方を行っていましたが、現在は以下のように分離されています:
shouldHideNote(): Note を隠すべきかを判定する public メソッド
public async shouldHideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<boolean> {
if (meId === packedNote.userId) return false;
if (this.isSigninOrTimeHidden(packedNote, meId)) {
return true;
}
if (packedNote.visibility === 'specified' && !this.isSpecifiedVisibleTo(packedNote, meId)) {
return true;
}
if (packedNote.visibility === 'followers' && !(await this.isFollowerVisibleTo(packedNote, meId))) {
return true;
}
return false;
}
hideNote(): Note 内容を隠す public メソッド(変更のみ実行)
public hideNote(packedNote: Packed<'Note'>): void {
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.isHidden = true;
}
ヘルパーメソッドの導入#
可視性ロジックを整理するために、以下のヘルパーメソッドが追加されました:
- isSpecifiedVisibleTo(): 指名可視性(visibility: specified)の判定
- isSigninOrTimeHidden(): サインイン要件と時間制限の判定
- isFollowerVisibleTo(): フォロワー可視性(visibility: followers)の判定
パフォーマンス最適化#
isFollowerVisibleTo()メソッドは、データベースクエリの代わりにuserFollowingsCacheを使用してフォロー関係をチェックするように最適化されました:
const followings = await this.cacheService.userFollowingsCache.fetch(meId);
if (Object.hasOwn(followings, packedNote.userId)) {
return true;
}
これにより、ストリーミング配信時のフォロワー可視性チェックのパフォーマンスが大幅に向上します。
packed note への replyUserId フィールド追加#
Note エンティティはreplyUserId フィールドを含むようになり、リプライの文脈をより効率的に判定できます:
replyUserId: note.reply?.userId ?? note.replyUserId,
5.2 NoteStreamingHidingService#
NoteStreamingHidingServiceは、ストリーミング配信時の Note 可視性を処理する新しい専用サービスです。このサービスは、多層リノート構造における可視性ルールに基づいて Note を隠すべきか、またはスキップすべきかを判定します。
3 層の隠蔽システム#
サービスは、ネストされたリノート構造に対応する 3 層の隠蔽レイヤーを実装しています:
- 'note' レイヤー: トップレベルの Note 自体
- 'renote' レイヤー: note.renote(1 階層目のリノート)
- 'renoteRenote' レイヤー: note.renote.renote(2 階層目のリノート)
type HiddenLayer = 'note' | 'renote' | 'renoteRenote';
shouldHide () メソッド:多層可視性評価#
shouldHide () メソッドは、ネストされたリノート階層全体にわたって可視性を評価します:
public async shouldHide(
note: Packed<'Note'>,
meId: MiUser['id'] | null,
): Promise<LockdownCheckResult>
判定ロジック:
-
1 階層目(note 自体):
- 可視性違反かつ純粋リノート → ストリームをスキップ
- 可視性違反かつ引用リノート →
'note'レイヤーを隠す - 可視性違反かつ通常 Note →
'note'レイヤーを隠す
-
2 階層目(note.renote):
- note が引用リノートで、renote が可視性違反 →
'renote'レイヤーを隠す - note が純粋リノートで、renote が可視性違反 → ストリームをスキップ
- note が引用リノートで、renote が可視性違反 →
-
3 階層目(note.renote.renote):
- note.renote が引用リノートで、renote.renote が可視性違反 →
'renoteRenote'レイヤーを隠す - note.renote が純粋リノートで、renote.renote が可視性違反 → ストリームをスキップ
- note.renote が引用リノートで、renote.renote が可視性違反 →
純粋リノート vs. 引用リノートの処理の違い:
- 純粋リノート: 可視性違反がある場合、Note 全体をスキップ(配信しない)
- 引用リノート: 可視性違反がある場合、違反部分のコンテンツを隠すが配信は継続
これにより、ユーザーは引用リノートの存在を知ることができますが、閲覧権限のない引用元コンテンツは保護されます。
applyHiding () メソッド:コンテンツ変更#
applyHiding () メソッドは、判定された隠蔽レイヤーに基づいて Note オブジェクトを変更します:
public applyHiding(
note: Packed<'Note'>,
hiddenLayers: Set<HiddenLayer>,
): void {
if (hiddenLayers.has('note')) {
this.noteEntityService.hideNote(note);
}
if (hiddenLayers.has('renote') && note.renote) {
this.noteEntityService.hideNote(note.renote);
}
if (hiddenLayers.has('renoteRenote') && note.renote && note.renote.renote) {
this.noteEntityService.hideNote(note.renote.renote);
}
}
このメソッドは渡されたnoteオブジェクトを直接変更し、各レイヤーでNoteEntityService.hideNote()を呼び出します。
processHiding () メソッド:統合フロー#
processHiding () メソッドは、判定と適用を組み合わせた便利なメソッドです:
public async processHiding(
note: Packed<'Note'>,
meId: MiUser['id'] | null,
): Promise<{ shouldSkip: boolean }> {
const result = await this.shouldHide(note, meId);
if (result.shouldSkip) {
return { shouldSkip: true };
}
this.applyHiding(note, result.hiddenLayers);
return { shouldSkip: false };
}
これは、ストリーミングチャネルでの使用に最適化されたワンステップインターフェースを提供します。
5.3 ストリーミングチャネル接続の強化#
非同期初期化とブーリアン戻り値#
Channel.init () メソッドのシグネチャが拡張され、接続の有効性を示すブーリアン値を返せるようになりました:
public abstract init(params: JsonObject): Awaitable<void | boolean>;
戻り値の意味:
void/Promise<void>: チェックなし(常に接続を許可)true/Promise<true>: 接続可能false/Promise<false>: 接続不可(接続を切断)
これにより、チャネルは接続確立前に早期可視性・サインインチェックを実行できます。
Connection.connectChannel () の非同期化#
Connection.connectChannel () メソッドが非同期になり、初期化結果に基づいて接続を拒否できるようになりました:
public async connectChannel(id: string, params: JsonObject | undefined, channel: string, pong = false) {
// ...
const valid = await ch.init(params ?? {});
if (typeof valid === 'boolean' && !valid) {
// 初期化処理の結果、接続拒否されたので切断
this.disconnectChannel(id);
return;
}
// ...
}
早期可視性フィルタリング#
Connection.onNoteStreamMessage()は、ストリーミングイベントのメタデータを使用して早期フィルタリングを実行します:
private async onNoteStreamMessage(data: GlobalEvents['note']['payload']) {
// 自分自身ではないかつ
if (data.body.userId !== this.user?.id) {
// 公開範囲が指名で自分が含まれてない
if (data.body.visibility === 'specified' &&
(this.user == null || (!data.body.visibleUserIds.includes(this.user.id) &&
!(data.body.mentions?.includes(this.user.id) ?? false)))) {
return;
}
// 公開範囲がフォロワーで自分がフォロワーでない
if (data.body.visibility === 'followers' &&
(this.user == null || (!Object.hasOwn(this.following, data.body.userId) &&
data.body.replyUserId !== this.user.id &&
!(data.body.mentions?.includes(this.user.id) ?? false)))) {
return;
}
}
// ...
}
これにより、各チャネルに配信する前に、接続レベルで不適切な Note がフィルタリングされます。
チャネル管理の最適化#
チャネルストレージが配列からMap<string, Channel>に変更され、接続管理のパフォーマンスが向上しました:
private channels: Map<string, Channel> = new Map();
これにより、O (n) の線形検索から O (1) の定数時間検索に改善されます。
5.4 タイムラインチャネルでの可視性適用#
各タイムラインチャネル(home-timeline、local-timelineなど)は、NoteStreamingHidingServiceを統合しています:
const { shouldSkip } = await this.noteStreamingHidingService.processHiding(note, user.id);
if (shouldSkip) return;
Channel.isNoteVisibleForMe () ヘルパー#
Channel ベースクラスは、QueryService.generateVisibilityQueryと同期された可視性チェックヘルパーを提供します:
protected isNoteVisibleForMe(note: Packed<'Note'>): boolean {
const meId = this.connection.user?.id ?? null;
if (note.visibility === 'specified') {
if (meId == null) {
return false;
} else if (meId === note.userId) {
return true;
} else {
return (note.visibleUserIds?.some(id => meId === id) ?? false)
|| (note.mentions?.some(id => meId === id) ?? false);
}
}
if (note.visibility === 'followers') {
if (meId == null) {
return false;
} else if (meId === note.userId) {
return true;
} else if (note.reply && (meId === note.reply.userId)) {
return true;
} else if (note.mentions && note.mentions.some(id => meId === id)) {
return true;
} else {
return Object.hasOwn(this.following, note.userId);
}
}
return true;
}
このヘルパーにより、タイムラインチャネル全体で一貫した可視性チェックが保証されます。
6. これらのパターンが UI 応答性とロード安定性を向上させる仕組み#
6.1 UI 応答性の利点#
Async/Await によるメインスレッドブロッキング防止:
- すべての取得操作は Promise で即座に返される
- ネットワークリクエスト中も UI レンダリングが中断されない
- 重い読み込み中でもユーザーインタラクションが応答性を保つ
遅延読み込みによる作業の延期:
- ビューポート付近の Note のみが完全にレンダリングされる
- IntersectionObserver がユーザーのスクロールに応じて自動的に読み込みをトリガー
- ブラウザネイティブの content-visibility がレンダリング作業を削減
キュー管理による破壊的更新の防止:
- 新しい Note は即座に挿入されずにキューに蓄積される
- ユーザーがキューに入った更新をいつロードするかを制御
- 読書体験が安定し予測可能になる
6.2 ロード安定性の利点#
個別のローディング状態:
- 初期ロードはコンテンツフラッシュなしでスピナーを表示
- 追加ページロードが既存コンテンツを非表示にしない
- エラー状態がコンテキストを失うことなくリトライオプションを提供
バッチ処理:
- 複数の保留中の Note が並列に取得される
- Promise.allSettled が 1 つの失敗が他をブロックすることを防ぐ
- フィボナッチバックオフによる自動リトライが一時的な障害を処理
バックグラウンドタブ最適化:
- 非表示タブで 10 秒後に更新を一時停止
- 非アクティブなタイムラインのメモリと CPU 使用量を削減
- タブがアクティブになるとスムーズに再開
分散ロック:
- サーバーインスタンス間での冗長な取得を防止
- Note 作成における競合状態を排除
- 可用性を犠牲にすることなくデータ一貫性を保証
6.3 パフォーマンス特性#
最小化された Note ストリーミング:
- WebSocket は必須データ(ID)のみを配信
- 完全な Note データはオンデマンドで取得
- 初期ペイロードサイズを劇的に削減
- Time To Interactive (TTI) を改善
CSS 最適化:
- Content-visibility によりブラウザがレンダリング計算をスキップ
- Contain-intrinsic-size が完全なレイアウトなしでサイズヒントを提供
- 数千の Note を通じたスムーズなスクロールを実現
フィボナッチ vs. 指数バックオフ:
- 指数(2^n)よりも緩やかな進行
- 一時的なネットワーク問題からの高速リカバリ
- 初期リトライの総待機時間が短く、より良いユーザー体験
6.4 ストリーミング可視性制御の利点#
早期フィルタリング:
- 接続レベルでの可視性チェックにより、不適切な Note がチャネルに到達する前にブロック
- 追加のデータベースクエリなしで、メタデータベースのフィルタリングが実行される
- チャネル処理オーバーヘッドを削減
多層リノート処理:
- ネストされたリノート構造の各レイヤーで個別の可視性判定
- 純粋リノートは可視性違反時に完全にスキップ
- 引用リノートは違反部分のみを隠して配信を継続
- ユーザー体験と情報セキュリティのバランスを実現
パフォーマンス最適化:
userFollowingsCacheの使用によりフォロワーチェックが高速化- チャネル管理が
Mapベースになり O (1) の検索時間 - モジュール化されたヘルパーメソッドによりコードの再利用性が向上
まとめ#
Misskey の Note 取得アーキテクチャは以下を組み合わせています:
- 一元化されたサービス (ApNoteService, MkTimeline, MkPagination) による一貫した取得ロジック
- 多層重複防止 分散ロック、データベースチェック、制約、エラーリカバリを使用
- フィボナッチバックオフリトライ 指数戦略よりも緩やかな進行
- 非同期パターン Promise ベースの API、遅延読み込み、キュー管理を含む
- UI 最適化 IntersectionObserver と CSS content-visibility を活用
- ストリーミング可視性制御 NoteStreamingHidingService による多層リノート処理と早期フィルタリング
これらのパターンが連携して以下を保証します:
- 応答性: 取得中に UI がブロックされない
- 安定性: ローディング状態とキューが破壊的な更新を防止
- 効率性: 遅延読み込みとバッチ処理が不要な作業を最小化
- 信頼性: リトライロジックと重複防止が障害を適切に処理
- セキュリティ: 可視性ルールの厳格な適用により情報漏洩を防止