Shopify ブログ

GraphQLで実現するShopifyメタフィールドからの迅速なデータ取得

※このブログはShopifyアプリパートナーの寄稿です。

Shopifyアプリ開発者のみなさんは、マーチャントが追加の商品データをストアのメタフィールドに保存し続けていることに気づいているでしょう。これは開発者にとって頭の痛い問題になります。というのも、Shopify REST APIでメタフィールドからデータを取得することは周知のとおり困難で、またクライアントのストアデータと同期する際にパフォーマンスの低速化も引き起こすからです。

わたしたちのアプリ、Klevu Searchでは、マーチャントの商品データを取得して検索用にインデックスしています。当初はメタフィールドと格闘していましたが、慎重なクエリの作成とリソースマネジメントにより、GraphQLRESTよりかなり速く商品のメタフィールドデータを読み込めることがわかりました。しかし一方で、クエリコストとスロットリングのせいでGraphQLRESTより遅くなるという限界もあります。

とはいえ、メリットは確かにあるのです。いくつかのテストケースにおいて、商品メタフィールドの同期タイムが4分から10秒にまで削減されました。この記事では、それを実現した方法と、その過程で得られた有益な情報を共有したいと思います。 

REST APIによるShopifyメタフィールドの取得

REST APIで商品のメタフィールドデータを取得することの問題点は、バルク(一括)処理ができないことです。Shopifyキュメントには、/metafields.json?metafield[owner_resource]=productなどの有力な手がかりがありますが、実践においてはこの方法だと期待するデータが得られません。

結局、一度に1つの商品データまたは1つの商品バリエーションを取得するだけになります。つまり、クライアントが100の商品と400のバリエーションを持っている場合、すべてのメタフィールドデータを取得するために500APIコールが必要です。これには4分かかり、長過ぎです。

GraphQLによるShopifyメタフィールドの取得

幸い、ShopifyにはGraphQL APIがあり、これならデータ取得のバルク処理という点においてより柔軟で、効率的に商品メタフィールドを得られます。

とはいえ、RESTコールを相当のGraphQLコールに置き換えれば良いというだけの問題ではありません。慎重なクエリ作成とリソースマネジメントが必要になり、この点を以下のセクションで詳述します。

クエリコスト

もっとも理解すべき重要な点は、GraphQLのクエリコスト面です。Shopifyドキュメントにさらに詳しい情報がありますが、まず知っておくべきことをこの記事で解説します。

GraphQL50商品の商品名と説明を取得するクエリは次のようになります。

ここで、このクエリ結果の各要素に関連したリソースコストがあります。商品数を50から100に増やすと、全体のクエリコストも上がり、商品数を10に減らせばコストも下がります。

Shopifyドキュメントでは以下のように記載されています。

「スキーマの各フィールドには、それぞれに割り当てられた整数のコスト値があります。クエリのコストは各フィールドのコスト値の合計です。コネクションフィールドには、最初または最後の引数に基づくサブセレクションのコスト相乗効果があります。」

一度にできる限り複数の商品メタフィールドを取り出すベストケースのクエリを見てみましょう。ちなみに、Shopify APIで通常取得できる最大レコード数は250です。

上記のクエリでは、250の商品とそれぞれに関連するメタフィールドの値をさらに250、リクエストしています。この単一のAPIコールの結果、一度に250の商品データを得ることになるはずなので、RESTの一度に1商品のアプローチに比べると素晴らしい結果です。

しかしクエリを実行すると、問題が発生して以下のAPIレスポンスが返ってきます。

Error: Query has a cost of 63252, which exceeds the max cost of 1000

何が起きているのでしょうか? Shopifyドキュメントを確認してみます。

「アプリに与えられている容量は1,000コストポイントです。つまり、クエリの総コストは1,000ポイントを超えることができません。」

今回のクエリはShopifyが対応できる量の63倍複雑になってしまいました。もっとシンプルにする必要があります。

言い換えると、リクエストするレコード数を減らさなければなりません。

250商品で50のメタフィールド=13,252クエリコスト

まだ多過ぎます。

50商品で50のメタフィールド=2,652クエリコスト

かなり近づきました。

30商品で30のメタフィールド=692クエリコスト

これならいけます!

ストアのメタフィールドが30以下であれば、効率的に30商品の値を一度に取得できます。一方、REST APIの場合は一度に1商品ずつです。

スロットリング

以上を踏まえると、GraphQLによるデータの同期はREST30倍速いことになりますね?

残念ながら、ことはそうシンプルではありません、上記のクエリを続けざまに2回実行すると(たとえば、最初の30商品の後に続けてさらに30商品)、APIレスポンスは次のようになります。

Error: Throttled

1,000ポイントのうち、すでに700ポイント近く使用しているので、次のリクエストが可能なポイントが得られるまで待つ必要があるのです。回復レートは1秒で50ポイントです。

Shopifyドキュメントを見てみましょう。

「アプリに与えられた容量は1,000コストポイントですが、リークレートが1秒間に50コストポイントとなります。つまり、クエリの総コストは一定時点で1,000ポイントを超えることはできませんが、アプリの空き容量が1秒で50ポイント作成されます。」

REST API100商品と400バリエーションを取得するのに4分かかったことを思い出してください。これと、上記のGraphQLクエリを使用した場合とを比較してみましょう。

ここでわかるように、GraphQLはバルクでメタフィールドを取得する能力があるにもかかわらず、100の親商品のメタフィールドの取得に38秒もかかっています。さらにまだ商品バリエーションの取得も必要です。

上記を基に考えると、30の結果を得るGraphQLの各リクエストごとに14.5秒かかります。そしてすべての400バリエーションを得るには14GraphQLリクエストが必要で、それには追加で203秒を要します。

つまり、このアプローチだと500レコードのメタフィールド取得に4分近くかかるので、残念なことにREST APIの場合とほぼ同じなのです。

こちらも参考にしてください:GraphQL vs RESTShopifyパートナーがパフォーマンスと安定性を向上させるには

わたしたちのアプローチ

わたしたちのアプリでは、所与のShopifyストアに必要なメタフィールドはわかっていて、利用可能なキーと名前空間も把握しています。そのため、きわめて具体的なアプローチが可能です。

わたしたちが発見したもっとも効率的なGraphQLクエリは、特定のメタフィールドのみをリクエストして、絶対必要なデータ以外は取得しないというものです。以下のようになります。

pageInfo{hasNextPage}は次のページからさらにレコードを読み込むべきかどうかを知るために必要です。この例ではcursorフィールドを使用しています。legacyResourceIdは商品バリエーションのIDで、Product{legacyResourceId}は商品バリエーションを親要素に結びつけるために必要です。

3つのmetafieldsは、特定のnamespace:key値で、ストアに要求されるものです。この部分はストアに要求するメタフィールドの数に応じて増減します。

このクエリにより、必要な3つの特定メタフィールドを、一度に50商品分取得できます。

大きいページサイズと小さいページサイズ

こうしたクエリコストとスロットリングがあるので、一般的に考えられる「一度により多くの商品を処理したほうが速い」という見解は必ずしも正確でないことになります。

ページサイズが少なければクエリコストも低く、スロットリングを避けながら多くのリクエストが可能になります。ただしリクエストごとに返ってくるデータは少なくなります。ページサイズが大きければ、リクエストで得られるデータは多いですが、クエリコストも高まり、スロットルも多くなります。

単一のメタフィールドの100商品と400バリエーションを取得するテストの結果、わかったのは以下のことです。

・ページごとに1商品の場合:216秒、スロットル0

・ページごとに5商品の場合:44秒、スロットル0

・ページごとに10商品の場合:21秒、スロットル0

ページごとに25商品の場合:10秒、スロットル0

ページごとに50商品の場合:10秒、スロットル3

・ページごとに75商品の場合:12秒、スロットル6

ページごとに100商品の場合:10秒、スロットル4

・ページごとに150商品の場合:16秒、スロットル7

ページごとに200商品の場合:10秒、スロットル5

・ページごとに250商品の場合:18秒、スロットル7

ご覧のように所要時間とスロットル回数はそろっていません。トータル時間は3つの要素の組み合わせで決定します。

1 取得するメタフィールドの数

2 最適なページネーションの数

3 スロットルの回数

この例では、ベストタイムは最適なAPIリクエストの数に依存しています。100商品と400バリエーションを、2550100200に分割した場合にページごとにきれいに分けられます。一方、1510もきれいに分割できますが、個々のリクエストで得られる商品の最適な閾値を下回るために速度が遅くなっています。

重要なのは、ページごとに商品数2550100200のデータを取得する場合、すべてタイムが同じであることです。これは、ページサイズが増すのに応じてスロットルが多くなることに起因します。

結果として、わたしたちはつねにページあたり25の商品をリクエストすることにしました。

総合結果

要約すると、少数の商品メタフィールドをストアから取得する場合に大きな改善が見られました。GraphQLはクエリコストが低く、とても効率的なバルク取得が可能です。

100商品と400バリエーションがあるShopifyストアからページあたり25の結果を取得した場合、REST APIと比較して以下のような同期タイムのデータが得られました。RESTの場合は4分(240秒)かかっていたことを思い出してください。

1メタフィールド:~10

2メタフィールド:~20

5メタフィールド:~50

10メタフィールド:~100

20メタフィールド:~200

ここでは明らかな「メタフィールド数×10秒」のパターンが見られます。

24メタフィールド:~240秒(REST APIと同じ)

30メタフィールド:~300秒(REST APIより遅い)

24メタフィールド以上の場合、GraphQLのメリットはスロットリングのために失われ、REST APIのほうが速いという結果になります。そのため、特定のストアが必要とするメタフィールドの数に応じて、RESTGraphQLを使い分けることになるでしょう。

10,000以上の商品と10以上のメタフィールドを持つ実際のストアをいくつか検証した結果、GraphQLのアプローチを用いると、Shopifyスタンダードストアのフルデータの同期タイムが3時間から1時間に、同様のShopify Plusストアの場合は1時間45分からわずか35分になりました。

GraphQLのバルクオペレーションでShopifyメタフィールドの高速取得が可能に

GraphQLによる改善方法を発見しただけでも出発点としては十分ですが、さらにShopifyで可能なデータ取得オプションを追求してみましょう。たとえば、GraphQLバルクオペレーションAPIなどがあります。

このアプローチは、GraphQLクエリを使用しながら少し異なる方法を取ります。レスポンスにおいて直接結果を受け取るのではなく、参照IDを受け取るのですが、このIDShopifyがデータの準備を終えているかどうかを定期的に確認するのに使用できます。タスクが完了すると、JSONLフォーマットで結果をダウンロードできるURLが提供されます。

わたしたちの最初のテストでは、このアプローチによるさらに良いパフォーマンス結果が得られました。Shopifyのバルクミューテーションオペレーションを使用することで、1つのAPIコールで以下のデータが取得できます。

・すべての商品

・すべての商品バリエーション

・すべての商品メタフィールド

・すべての商品バリエーションメタフィールド

クエリコストは10で、完了までにかかる時間は30だけです。このアプローチの場合、レート制限、ページネーション、スロットリングの心配も不要です。RESTあるいはスタンダードなGraphQLコールよりもさらに効率的でパフォーマンスの高いオプションとなっています。

 

原文:Joseph McDermott 翻訳:深津望

 

Shopifyパートナープログラムでビジネスを成長させる
マーケティング、カスタマイズ、またはWebデザインや開発など提供するサービスに関係なく、Shopifyパートナープログラムはあなたを成功へと導きます。プログラムの参加は無料で、収益分配の機会が得られ、ビジネスを成長させる豊富なツールにアクセスできます。情熱的なコマースコミュニティに今すぐ参加しましょう!

今すぐ登録