---
title: "Solanaアーカイブデータ: ブロックとトランザクションの全履歴をクエリする方法"
description: "Solanaアーカイブデータの解説: ノードが履歴をプルーニングする理由、アーカイブアクセスが必要なRPCメソッド、大規模にブロックとトランザクションの全履歴をクエリする方法。"
---

# Solanaアーカイブデータ: ブロックとトランザクションの全履歴をクエリする方法

<ImageBlock
  src="https://media.alchemy.com/blog/solana-archival-data-hero.png"
  alt="Solana アーカイブデータ：完全なブロックとトランザクション履歴のクエリ"
  width={1920}
  height={900}
  priority
/>

Solanaをインデックスするチームは、遅かれ早かれ同じ壁にぶつかる。数か月前のトランザクションをノードに問い合わせると、「block cleaned up」エラーが返ってくる。ノードがその期間の台帳を数週間前に削除しているからだ。標準的なSolana RPCノードは[直近数日分程度のチェーン](https://docs.anza.xyz/implemented-proposals/rpc-transaction-history)しか保持しておらず、ディスク容量の予算内に収めるためにそれより古いデータはすべて削除する。Solanaがピーク時に[年間4ペタバイト超のデータ](https://www.alchemy.com/blog/zero-downtime-zero-gaps-solana-grpc-streaming)を生成することを考えれば、1台のマシンで全てを保持できるはずがない。

アーカイブデータとは、ノードが破棄したものすべてに到達するための手段だ。まず、はっきりさせておくべき定義がある。Solanaのアーカイブデータは古いブロックとトランザクションであって、過去のアカウント状態ではない。Ethereumから来た人にとっては、アーカイブノードが任意のアカウントの残高を過去の任意のブロック時点で報告できるため、この違いは重要だ。存在しないメソッドを前提にパイプラインを設計してしまうと、最初につまずくことになる。残りは実践的な話だ。Solanaの完全な履歴が実際にどこにあるのか、どのRPCメソッドがそこに到達するのか、それらをどうクエリするのか、そしてgenesisから欠落なくインデックスをバックフィルする方法について解説する。

## 標準的なSolanaノードが完全な履歴を提供できないのはなぜか？

バリデータは台帳をローカルデータベースに書き込み、運営者はディスクが埋まらないよう[`--limit-ledger-size`フラグ](https://docs.anza.xyz/operations/guides/validator-start)を付けて実行する。このフラグが設定されると、ノードは最も古いデータから優先的に削除する。デフォルト値はshred 2億個分で、shredはSolanaがネットワーク伝播のためにブロックを分割する単位であり、これにより台帳はおおよそ500GB未満に保たれる。このフラグがなければ、ノードはディスクが尽きるまで受信したデータをすべて保持するが、Solanaのデータレートではそう長くは持たない。

[Agaveバリデータクライアント](https://docs.anza.xyz/implemented-proposals/rpc-transaction-history)を保守するチーム、Anzaはこの理由について率直に述べている。6か月分のトランザクションデータをバリデータのローカル台帳に実用的に保存することはできず、ノードが保持する履歴は「日単位のオーダー」だという。それより古いものはすべて別の場所に置かれる必要がある。

任意のノードの下限は[`minimumLedgerSlot`](https://www.alchemy.com/docs/reference/solana-api-quickstart)を呼び出すことで確認でき、これはそのノードがまだ保持している最も古いslotを返す。しばらく監視すればこの数字は上がり続ける一方だとわかる。プルーニングが止まることはないからだ。それより下のslotを問い合わせるとデータの代わりに「block cleaned up」エラーが返る。ディスクを大きくしてもこの状況は変わらない。チェーンの最新状態を提供することと、深い履歴を提供することは異なるインフラの課題であり、アーカイブシステムが存在するのは、後者がノードにできる範囲を超えて成長したからだ。

## Solanaにおけるアーカイブとは何か、Ethereumとどう違うのか？

[Ethereumのアーカイブノード](https://www.alchemy.com/overviews/archive-nodes)は状態トライのすべての過去バージョンを保持している。コントラクトのストレージやウォレットの残高が過去の任意のブロック時点でどうだったかを問い合わせることができ、ノードはまさにその目的のために保持していたデータから回答する。Ethereumから来たチームは、Solanaにも同等のものがあると考えがちだ。しかし実際にはそうではなく、この一つの思い込みが他の何よりも多くの過去データ計画を頓挫させている。

Solanaはアカウント状態をその場で上書きする。アカウントが変更されると新しいバージョンが古いバージョンに置き換わり、[AccountsDB内のバックグラウンドクリーニングプロセス](https://www.anza.xyz/blog/a-deep-dive-into-solana-s-accountsdb)が、後続のslotがファイナライズされると置き換えられたバージョンをガベージコレクションする。3か月前にアカウントが何を保有していたかの記録は残らない。だからこそSolana RPC APIには「slot NでのバランS」を返すメソッドが存在せず、[過去のアカウント状態のクエリ](https://github.com/solana-labs/solana/issues/18197)はSolanaのリポジトリで何年も未解決の機能リクエストとして残っている。

アーカイブインフラが保持しているのは台帳そのもの、つまりブロックとその中のトランザクションだ。アーカイブはブロック150,000,000番を渡すことも、あるアドレスに関わったすべてのトランザクションを渡すこともできる。しかし、昨年3月時点のウォレットのUSDC残高を渡すことはできない。この問いには依然として答えられるが、それはノードに保持していない状態を尋ねるのではなく、[インデクサー](https://www.alchemy.com/overviews/blockchain-indexer)を通じてウォレットのトランザクション履歴を再生することによって答えることになる。

## どのRPCメソッドがアーカイブデータを必要とするか？

あるメソッドが対象とするslotがノードのローカル下限を下回った瞬間、そのメソッドはアーカイブ読み取りになる。以下は長期ストレージに到達するメソッドだ。

<EmbeddedTable
  table={{
    columns: [
      { key: "1", width: 200, title: "Method", dataType: "object" },
      { key: "2", width: 260, title: "What it returns", dataType: "object" },
      { key: "3", width: 300, title: "Limit to know", dataType: "object" },
    ],
    data: [
      {
        "1": {
          title: "<p><strong>getBlock</strong></p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title: "<p>あるslotの完全なブロックとそのトランザクション</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title: "<p>maxSupportedTransactionVersion: 0のみ受け付けられる</p>",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        "1": {
          title: "<p><strong>getTransaction</strong></p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title: "<p>署名で指定した確定済みトランザクション</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title:
            "<p>processedコミットメントは拒否される。見つからない場合はnullを返す</p>",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
      {
        "1": {
          title: "<p><strong>getSignaturesForAddress</strong></p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title: "<p>あるアドレスを参照する署名を新しい順に</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title:
            "<p>1回の呼び出しにつき1から1,000件まで。beforeとuntilでページネーション</p>",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        "1": {
          title: "<p><strong>getBlocks</strong></p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title: "<p>範囲内の確定済みslot</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title: "<p>範囲は500,000 slotに制限される</p>",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        "1": {
          title: "<p><strong>getBlockTime</strong></p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title: "<p>ブロックの推定生成時刻</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title: "<p>タイムスタンプが記録されていない場合はnullを返す</p>",
          tooltip: "",
          icon: "",
        },
        id: 4,
      },
      {
        "1": {
          title: "<p><strong>getFirstAvailableBlock</strong></p>",
          tooltip: "",
          icon: "",
        },
        "2": {
          title: "<p>ストレージから取得可能な最も低いslot</p>",
          tooltip: "",
          icon: "",
        },
        "3": {
          title: "<p>ノードではなくアーカイブの下限を示す</p>",
          tooltip: "",
          icon: "",
        },
        id: 5,
      },
    ],
  }}
/>

ほとんどの過去データパイプラインは、このうち2つのメソッドの上に構築されている。`getSignaturesForAddress`であるアドレスの履歴を過去にさかのぼってページ送りし、`getTransaction`で各トランザクションの詳細を取得する。署名取得の呼び出しは[1回あたり最大1,000件](https://www.alchemy.com/docs/reference/solana-api-quickstart)しか返さないため、アクティビティの多いアドレスを最初のトランザクションまでさかのぼるには、長いページネーション呼び出しの連鎖が必要になり、そのほとんどがノードの最小台帳slotを超えた時点でアーカイブから提供されることになる。

これは弱いアーカイブの欠陥が露呈する場面でもある。プロバイダーの長期ストレージに穴があれば、バックフィルの途中の深い呼び出しのどこかで「slot skipped」や「missing in long-term storage」といったエラーが返ってくる。それをチェックしていなければ、誰も気づかないままインデックスが不完全になる。どのプロバイダーも同じメソッド名を公開している。異なるのは、その裏にあるストレージに穴があるかどうか、そして数千回の呼び出しでページ送りしているときにどれだけ速く応答するかだ。

## 完全なブロックとトランザクション履歴をどうクエリするか？

クエリ自体は通常のJSON-RPCだ。別途アーカイブ用のAPIはなく、履歴を解放する特別なパラメータもない。チェーンの最新状態を問い合わせるのと同じメソッドを呼び出し、slotがノードのローカル下限を下回っていれば、プロバイダーのアーカイブが応答する。

深い履歴から1つのブロックを取得する場合は次のようになる。

<CodeSnippet
  language="bash"
  theme="dark"
  code={`curl https://solana-mainnet.g.alchemy.com/v2/YOUR_API_KEY \\
  -X POST \\
  -H "Content-Type: application/json" \\
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "getBlock",
    "params": [150000000, {
      "maxSupportedTransactionVersion": 0,
      "transactionDetails": "full",
      "rewards": false
    }]
  }'`}
/>

レスポンスにはブロック全体、その中のすべてのトランザクション、各トランザクションのステータスとメタデータが含まれる。すべての呼び出しでパラメータに`maxSupportedTransactionVersion: 0`を含めること。これを含めないと、バージョン付きトランザクションを含むブロック（mainnetではほとんどがこれに該当する）でエラーになる。

あるアドレスの完全な履歴を辿るには、上記の表にある2つのメソッドのパターンをループで実行する。`getSignaturesForAddress`で空になるまで過去にさかのぼってページ送りし、各トランザクションの詳細を取得する。

<CodeSnippet
  language="typescript"
  theme="dark"
  code={`import { createSolanaRpc, address, type Signature } from "@solana/kit";
const rpc = createSolanaRpc(
  "https://solana-mainnet.g.alchemy.com/v2/YOUR_API_KEY"
);
const target = address("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4");
let before: Signature | undefined;
const signatures: Signature[] = [];
while (true) {
  const page = await rpc
    .getSignaturesForAddress(target, { before, limit: 1000 })
    .send();
  if (page.length === 0) break;
  signatures.push(...page.map((entry) => entry.signature));
  before = page[page.length - 1].signature;
}
// Resolve each signature to full transaction detail
const tx = await rpc
  .getTransaction(signatures[0], {
    maxSupportedTransactionVersion: 0,
    encoding: "jsonParsed",
  })
  .send();`}
/>

単一のウォレットであれば、このループだけで十分であり、ラップトップからでも問題なく実行できる。しかし、アドレスが数百万件の署名を持つアクティブなプログラムだったり、チェーン上のすべてのアドレスの履歴が必要になったりすると、これだけでは足りなくなる。それがバックフィルの課題であり、以下のセクションではデータの出所と、欠落なく読み込む方法について説明する。

## Solanaの完全な履歴は実際どのように保存されているか？

どのバリデータも台帳全体を保持していないため、長期履歴はノードの外に完全に存在している。実際にはこれを扱うシステムが2つある。

長らく使われてきたのはウェアハウス方式だ。専用のノードがファイナライズされたブロックを継続的に[Google Bigtable](https://docs.anza.xyz/operations/setup-an-rpc-node)にアップロードし、RPCノードがもう保持していないslotへのクエリを受け取ると、そのストアにフォールバックする。最近のslotはノードのローカルデータベースから、それより古いものはBigtableから返される。本番稼働しているSolana RPCの大半は長年この方式で深い履歴を提供してきた。この方式の代償は集中だ。チェーン全体の過去が最終的に1つの独自クラウドデータベースに収まってしまう。

新しい方式は、TritonとYellowstoneプロジェクトが主導するオープンソースアーカイブ、[Old Faithful](https://old-faithful.net/)だ。genesis以降のすべてのブロックをコンテンツアドレス指定のCARファイルにまとめ、IPFS、Filecoin、S3互換ストレージに分散させ、標準のSolana JSON-RPCおよびgRPCで提供する。このプロジェクトは、Solanaの完全な履歴が特定の一企業のデータベースに依存しないようにするために存在しており、genesisからの検証可能なカバレッジを必要とするチームはこれをリファレンスアーカイブとして扱う。

プロバイダーの背後にあるバックエンドがどちらであれ、理解しておくべきなのは、アーカイブはノードとは別のシステムだということだ。深さと完全性はそのシステムの特性であり、思い込むのではなく直接尋ねる価値がある。

## アカウントとトークンの履歴を大規模に取得するにはどうすればよいか？

「大規模なSolana履歴」を求めるチームが実際に求めているのは、生のブロックであることはめったにない。彼らが欲しいのは、ウォレットの残高の時系列推移、あるアドレスが行ったすべての転送、トークンの保有者履歴全体であり、ダッシュボードや税務エクスポートを動かせるだけの速度で提供されるものだ。これらはいずれも台帳上にクエリ可能な形で存在しない。導出する必要がある。

この手順はプロジェクトが変わってもほぼ変わらない。アーカイブRPCからアドレスの完全なトランザクション履歴を取得し、それらを順番に再生し、その過程で関心のある状態を計算する。各トランザクション後の残高、所有権の変化、転送の流れなどだ。結果を自分のデータベースに書き込むことで、コストのかかる再生処理をリクエストごとではなく一度だけ行うようにする。どのチェーンでもこれはブロックチェーンインデクサーの仕事だ。Solanaでは、これが過去の状態に到達する唯一の方法になる。ノードは何も保持していないからだ。

最も鋭い問いである「特定のslot時点でウォレットが何を保有していたか」に対しては、直接の回答を提供する。[`getTokenAccountsByOwnerAtSlot`](https://www.alchemy.com/docs/chains/solana/solana-api-endpoints/get-token-accounts-by-owner-at-slot)は標準の`getTokenAccountsByOwner`の構文を保ちつつ`slot`パラメータを追加し、その時点のウォレットの正確なトークン残高を1回の呼び出しで返す。これは再生ではなく継続的に維持されているヒストリカルインデックスに基づいているため、時点指定の保有量については上記の再構築パイプライン全体が不要になる。[過去のSolanaトークン残高機能のローンチ記事](https://www.alchemy.com/blog/historical-solana-token-balances)がその仕組みを解説している。

その他のよくある導出形式、つまり時系列の残高、転送、トークンメタデータについては、[Data APIs](https://www.alchemy.com/docs/data)が事前計算済みの形で提供しており、インデックス構築のプロジェクト自体を省略できる。カスタムの導出データやパイプラインの完全な制御が必要な場合は自前のインデクサーを構築するとよい。標準的な形式でカバーできるならマネージドメソッドを使う。うまくいかないのは、素のノードに過去のアカウント状態を尋ねることだ。ノードはそれを保持したことがないからだ。機能する回答はすべて、履歴の上に構築されたインデックスから来ている。

## genesisからインデックスをバックフィルする際、破綻しないようにするにはどうすればよいか？

過去データパイプラインで難しいのは立ち上げ部分だ。インデックスは空で、数億のslotを読み込む必要があり、その間もチェーンは新しいブロックを生成し続ける。バックフィルとライブフィードがきれいに合流しないと、一方が終わった地点ともう一方が始まった地点の間にギャップが生じる。

多くのチームは、まずバックフィル全体をアーカイブRPCへのポーリングで始めるが、genesis規模になるとこれは苦しくなる。レート制限、数億slotにわたるリクエストごとのコスト、そして何も欠落していないことを証明するきれいな方法がない、といった問題だ。本番投入に耐えるパターンは、フェーズごとに異なるソースを使う。大量の履歴はアーカイブから直接取得し、[Jetstreamer](https://github.com/anza-xyz/jetstreamer)のようなツールがOld Faithfulから直接ストリーミングする。チェーンの最新状態はポーリングではなくライブストリームから取得する。

ライブ側では、Yellowstone互換の[Solana gRPCストリーム](https://www.alchemy.com/solana-grpc)が、アカウントやプログラム、署名でフィルタリングした新しいトランザクションとアカウント更新を、発生と同時にインデクサーへプッシュする。バックフィルとストリームの継ぎ目はパイプラインが漏れがちな場所であり、それを塞ぐのがリプレイだ。[当社のgRPC](https://www.alchemy.com/blog/introducing-alchemy-solana-grpc)では、クライアントが`from_slot`パラメータを使って再接続し、ダウンしていた間に見逃したslotを再受信できるため、接続断がデータの穴を残すことはない。また、[フェイルオーバー中もメッセージを取りこぼさないストリーミングレイヤー](https://www.alchemy.com/blog/zero-downtime-zero-gaps-solana-grpc-streaming)も構築しており、これはそうでなければインデクサーとは別にギャップ検知サービスを運用する必要が生じる作業だ。アーカイブが過去をカバーし、ストリームが最新状態をカバーすれば、リプレイが両者を縫い合わせ続ける。

## Solanaのアーカイブプロバイダーを選ぶ際、何を確認すべきか？

まず確定させるべきは深さであり、どのプロバイダーにも正確にこう尋ねる価値がある。genesisからインデックスしているのか、それとももっと最近の高さから前方に向けてなのか。下限を明示しない「完全な過去データ」はよくあり、多くの場合、その下限はバックフィルがそこで止まって初めて発見することになる。

残りのチェックリストは、バックフィルが実際にどう動くかから導かれる。完全性が重要なのは、1つの穴がその上に構築したインデックス全体を損なうからだ。速度が重要なのは、署名履歴を一通り辿るには数千回の連続したアーカイブ読み取りが必要であり、1呼び出しあたりのレイテンシが積み重なって時間単位、あるいは日単位の実時間になるからだ。標準のJSON-RPCが重要なのは、独自の過去データ用エンドポイントを使うとパイプラインが1つのベンダーに縛られてしまう一方、通常のRPCであれば書き換えが一切不要だからだ。そして価格が重要なのは、深い履歴が本質的に読み取り主体だからだ。

当社はこのチェックリストに沿ってアーカイブアクセスを構築した。[genesisからの完全なブロックおよびトランザクション履歴](https://www.alchemy.com/docs/reference/solana-api-quickstart)を標準のJSON-RPCで提供しており、[当社のベンチマーク](https://www.alchemy.com/blog/solana-infrastructure)では、過去データの`getTransaction`が他のプロバイダーより最大20倍速く動作したほか、`getBlock`も最大3倍速く、`getProgramAccounts`のような負荷の高い呼び出しでも最大10倍速いという結果が出ており、コード変更や独自メソッドは一切不要だ。[Solana上で最速のアーカイブメソッドをどう構築したかというエンジニアリングの解説記事](https://www.alchemy.com/blog/how-alchemy-built-the-fastest-archival-methods-on-solana)が、この数字の背後にあるアーキテクチャを取り上げている。深さ、稼働率、価格、ツールについての全体的な比較は、[9つのおすすめSolana RPCプロバイダーの選定ガイド](https://www.alchemy.com/overviews/solana-rpc)が業界全体をカバーしている。どのプロバイダーを選ぶにせよ、バックフィルを始める前に、genesisからの深さと完全性についての回答を書面で得ておくこと。

## AlchemyでSolanaの過去データパイプラインを構築する

機能する過去データパイプラインには2つのものが必要だ。genesisからバックフィルできるだけ深いアーカイブと、最新状態に追いつけるだけ速いストリームだ。当社は[Alchemy's Solana platform](https://www.alchemy.com/solana)の一部としてその両方を運用している。アーカイブ読み取りは標準のJSON-RPCで完全なブロックおよびトランザクション履歴をカバーしており、既存のSolanaクライアントをAlchemyのエンドポイントに向けるだけで移行が完了する。[Solana gRPCストリーミング](https://www.alchemy.com/solana-grpc)はYellowstone互換で再接続時のリプレイに対応しており、月額最低料金やプランの前提条件なしで、従量課金制でTBあたり75ドルという価格設定だ。

契約や営業との電話なしで、無料枠から始められる。Solanaインフラを構築するチームは、[$20M Solana Fund](https://www.alchemy.com/solana-20m-fund)を通じて最大25,000ドルのクレジットに応募することもできる。パイプラインが稼働し始めれば、ノードが破棄する履歴はもはや自分の問題ではなくなる。
