変更のベスト プラクティス

次のベスト プラクティスに沿って、パフォーマンスを最適化し、オペレーション間の依存関係を管理し、Google Ads API でリソースを変更する際のレスポンスを処理します。

一時リソース名

GoogleAdsService.Mutate と BatchJobService はどちらも、後続のオペレーションで参照できる一時リソース名をサポートしています。これにより、単一の変更リクエストまたはバッチジョブで、キャンペーンとそれに関連する広告グループ、広告、キーワードを作成できます。

同じ変更リクエストまたはバッチジョブ内で新しく作成されたリソースを参照するには、新しいリソースの resource_name フィールドに負の整数 ID(-1 や -2 など。0 は除く)を指定します。たとえば、バッチ リクエストでキャンペーンを作成する場合は、リソース名を customers/CUSTOMER_ID/campaigns/-1 に設定します。同じリクエスト内の後続のオペレーションで広告グループを作成する場合は、親キャンペーンとして customers/CUSTOMER_ID/campaigns/-1 を参照します。API は、作成時に生成された実際のキャンペーン ID に -1 を自動的に置き換えます。

使用上の制約

一時リソース名を使用する場合は、次のルールに注意してください。

  • 順序が重要: 一時リソース名は、定義した後にのみ参照できます。オペレーションのリストでは、依存オペレーション(広告グループの作成など)は、親リソースを作成するオペレーション(キャンペーンの作成など)の後に表示する必要があります。
  • 単一リクエストまたはバッチジョブのスコープ: 一時リソース名は、個別のジョブや変更リクエスト間で保持されません。以前のジョブまたは変更リクエストで作成されたリソースを参照するには、システムによって生成された実際のリソース名を使用します。
  • グローバル一意性: 単一のジョブまたは変更リクエスト内で、各一時リソース名は、すべてのリソースタイプで一意の負の整数を使用する必要があります。たとえば、同じリクエストでキャンペーンと広告グループの両方に -1 を割り当てることはできません。同じリクエストまたはバッチジョブ内で一時 ID を再利用すると、NewResourceCreationError.DUPLICATE_TEMP_IDS エラーが返されます。

ペイロードの例

1 回の API リクエストまたはバッチジョブでキャンペーン、広告グループ、広告を追加するとします。次の REST JSON の例に示すように、GoogleAdsService.Mutate または BatchJobService.AddBatchJobOperations リクエスト ペイロードで mutateOperations 配列を構造化できます(簡潔にするため、他の必要なリソース フィールドは省略しています)。

{
  "mutateOperations": [
    {
      "campaignOperation": {
        "create": {
          "resourceName": "customers/CUSTOMER_ID/campaigns/-1"
        }
      }
    },
    {
      "adGroupOperation": {
        "create": {
          "resourceName": "customers/CUSTOMER_ID/adGroups/-2",
          "campaign": "customers/CUSTOMER_ID/campaigns/-1"
        }
      }
    },
    {
      "adGroupAdOperation": {
        "create": {
          "adGroup": "customers/CUSTOMER_ID/adGroups/-2"
        }
      }
    }
  ]
}

この例では、次の重要な詳細を示します。

  • -1 はすでにキャンペーンに割り当てられているため、広告グループでは新しい一時 ID(-2)が使用されます。
  • 広告グループは customers/CUSTOMER_ID/campaigns/-1 を参照して、前のオペレーションで作成されたキャンペーンにリンクします。
  • adGroupAdOperation は customers/CUSTOMER_ID/adGroups/-2 を参照し、リクエストの後続のオペレーションで新しい広告が参照されないため、resourceName を省略します。

同じタイプのオペレーションをグループ化する

GoogleAdsService.Mutate を使用する場合は、親と子の依存関係を尊重しながら、繰り返される mutate_operations 配列内のリソースタイプごとにグループ オペレーションをまとめます。このメソッドは、異なるリソースタイプが検出されるまでオペレーションを順番に読み取り、同じタイプの連続するオペレーションをすべて 1 つのバックエンド サービス リクエストにバッチ処理します。

たとえば、繰り返し mutate_operations フィールドに 5 つのキャンペーン オペレーションと 10 個の広告グループ オペレーションを含めると、システムは 2 つのバックエンド呼び出しを実行します。1 つは 5 つのキャンペーン オペレーションの CampaignService、もう 1 つは 10 個の広告グループ オペレーションの AdGroupService です。

一方、[campaign, ad group, campaign, ad group] として順序付けしてオペレーションをインターリーブすると、4 つの別々のバックエンド呼び出しが行われます。インターリーブされた呼び出しは API のパフォーマンスを低下させ、大きなバッチでリクエスト タイムアウトが発生する可能性があります。

部分的な障害とバッチ上限を処理する

デフォルトでは、GoogleAdsService.Mutate は、単一のオペレーションが失敗した場合、リクエスト全体をロールバックします。同じリクエスト内の他のオペレーションが失敗した場合でも有効なオペレーションを commit するには、リクエストで partial_failure を true に設定し、レスポンスで partial_failure_error を調べます。partial_failure が true の場合、一時 ID(customers/CUSTOMER_ID/campaigns/-1 など)を定義する親オペレーションの検証が失敗すると、同じリクエストでその一時 ID を参照する依存子オペレーションも NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS で失敗します。詳細については、部分的な障害に関するガイドをご覧ください。

リクエスト サイズ、サブバッチ処理、レート制限にも注意してください。

  • リクエストとチャンクのサイズの上限: 1 つの GoogleAdsService.Mutate リクエストでは、10,000 件の変更オペレーション(リクエスト内のすべてのオペレーションが AdGroupCriterionOperation の場合は最大 20,000 件。上限を超えると RequestError.TOO_MANY_MUTATE_OPERATIONS を返す)と、最大 100 件のアクション オペレーション(RequestError.TOO_MANY_ACTION_OPERATIONS)の上限が適用されます。BatchJobService.AddBatchJobOperations では、呼び出しごとに最大 10,000 件のオペレーション、MutateOperation ごとに最大 10,484,504 バイト、AddBatchJobOperationsRequest ごとに最大 41,937,920 バイトの上限が適用されます(上限を超えると BatchJobError.REQUEST_TOO_LARGE を返す。バッチジョブあたりのオペレーションの合計数は最大 1,000,000 件)。同じキャンペーンまたはアカウントを対象とする同時変更は、DatabaseError.CONCURRENT_MODIFICATION エラーをトリガーする可能性があります。
  • BatchJobService アトミック サブバッチ: バッチジョブは部分的な失敗のセマンティクス(内部サブバッチあたり 1,000 オペレーションがデフォルト)で実行されますが、BatchJobService は同じ親 ID の特定の連続する依存オペレーションをアトミック サブバッチに自動的にグループ化します。
    • 同じ AssetGroup ID の連続する AssetGroupAssetOperation(create)オペレーションが続く AssetGroupOperation(create)(合計 1,000 件のオペレーションまで。BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE でアトミックに失敗します。各 AssetGroupOperation update または remove は、スタンドアロンの単一オペレーション サブバッチで実行されます)。
    • P-MAX の CampaignOperation(ブランド ガイドラインが有効な場合は create。brand_guidelines_enabled が false に設定されているか、hotel_property_asset_set が設定されている場合を除き、デフォルトで有効)の後に、同じ Campaign ID の連続した CampaignAssetOperation(create)オペレーション(合計 1,000 件までのオペレーション。BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE でアトミックに失敗)。
    • 同じ親(AssetGroup または AdGroup)に対する連続した AssetGroupListingGroupFilterOperation(max 10,000、BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE でアトミックに失敗)または AdGroupCriterionOperation(listing_group、max 20,000、CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION でアトミックに失敗)オペレーション。

レスポンスから変更可能な属性を取得する

ミューテート リクエストの response_content_type を MUTABLE_RESOURCE に設定すると、リクエストによって作成または更新(削除ではない)されたサポート対象のすべてのオブジェクトについて、レスポンスには resource_name と、その 変更可能なフィールド(および、返されたリソースのキー システム設定フィールド(ExperimentArm.in_design_campaigns など))が設定されたリソース オブジェクトが含まれます。remove オペレーションの場合、または MUTABLE_RESOURCE の戻りをサポートしていないリソースタイプの場合、レスポンスは常に resource_name のみを返します。この機能を使用すると、各 mutate 呼び出しの後に Search リクエストや SearchStream リクエストを追加で送信することを回避できます。

response_content_type を設定しない場合、Google Ads API はデフォルトで RESOURCE_NAME_ONLY になり、変更された各リソースの resource_name のみが返されます。

次の例は、ミューテート呼び出しから変更可能なリソースを取得する方法を示しています。

Java

private String createExperimentArms(
    GoogleAdsClient googleAdsClient, long customerId, long campaignId, String experiment) {
  List<ExperimentArmOperation> operations = new ArrayList<>();
  operations.add(
      ExperimentArmOperation.newBuilder()
          .setCreate(
              // The "control" arm references an already-existing campaign.
              ExperimentArm.newBuilder()
                  .setControl(true)
                  .addCampaigns(ResourceNames.campaign(customerId, campaignId))
                  .setExperiment(experiment)
                  .setName("control arm")
                  .setTrafficSplit(40)
                  .build())
          .build());
  operations.add(
      ExperimentArmOperation.newBuilder()
          .setCreate(
              // In standard campaign experiments, creating the treatment arm automatically
              // generates a draft campaign that you can modify before starting the experiment.
              ExperimentArm.newBuilder()
                  .setControl(false)
                  .setExperiment(experiment)
                  .setName("experiment arm")
                  .setTrafficSplit(60)
                  .build())
          .build());

  try (ExperimentArmServiceClient experimentArmServiceClient =
      googleAdsClient.getLatestVersion().createExperimentArmServiceClient()) {
    // Constructs the mutate request.
    MutateExperimentArmsRequest mutateRequest =
        MutateExperimentArmsRequest.newBuilder()
            .setCustomerId(Long.toString(customerId))
            .addAllOperations(operations)
            // We want to fetch the draft campaign IDs from the treatment arm, so the easiest way
            // to do that is to have the response return the newly created entities.
            .setResponseContentType(ResponseContentType.MUTABLE_RESOURCE)
            .build();

    // Sends the mutate request.
    MutateExperimentArmsResponse response =
        experimentArmServiceClient.mutateExperimentArms(mutateRequest);

    // Results always return in the order that you specify them in the request. Since we created
    // the treatment arm last, it will be the last result.  If you don't remember which arm is the
    // treatment arm, you can always filter the query in the next section with
    // `experiment_arm.control = false`.
    MutateExperimentArmResult controlArmResult = response.getResults(0);
    MutateExperimentArmResult treatmentArmResult =
        response.getResults(response.getResultsCount() - 1);

    System.out.printf(
        "Created control arm with resource name '%s'%n", controlArmResult.getResourceName());
    System.out.printf(
        "Created treatment arm with resource name '%s'%n", treatmentArmResult.getResourceName());

    return treatmentArmResult.getExperimentArm().getInDesignCampaigns(0);
  }
}

      

C#

private static (MutateExperimentArmResult, MutateExperimentArmResult)
    CreateExperimentArms(GoogleAdsClient client, long customerId, long baseCampaignId,
        string experimentResourceName)
{
    // Get the ExperimentArmService.
    ExperimentArmServiceClient experimentService = client.GetService(
        Services.V25.ExperimentArmService);

    // Create the control arm. The control arm references an already-existing campaign.
    ExperimentArmOperation controlArmOperation = new ExperimentArmOperation()
    {
        Create = new ExperimentArm()
        {
            Control = true,
            Campaigns = {
                ResourceNames.Campaign(customerId, baseCampaignId)
            },
            Experiment = experimentResourceName,
            Name = "Control Arm",
            TrafficSplit = 40
        }
    };

    // Create the non-control arm.
    // In standard campaign experiments, creating the treatment arm automatically
    // generates a draft campaign that you can modify before starting the experiment.
    ExperimentArmOperation treatmentArmOperation = new ExperimentArmOperation()
    {
        Create = new ExperimentArm()
        {
            Control = false,
            Experiment = experimentResourceName,
            Name = "Experiment Arm",
            TrafficSplit = 60
        }
    };

    // We want to fetch the draft campaign IDs from the treatment arm, so the
    // easiest way to do that is to have the response return the newly created
    // entities.
    MutateExperimentArmsRequest request = new MutateExperimentArmsRequest
    {
        CustomerId = customerId.ToString(),
        Operations = { controlArmOperation, treatmentArmOperation },
        ResponseContentType = ResponseContentType.MutableResource
    };

    MutateExperimentArmsResponse response = experimentService.MutateExperimentArms(
        request
    );

    // Results always return in the order that you specify them in the request.
    // Since we created the treatment arm last, it will be the last result.
    MutateExperimentArmResult controlArm = response.Results.First();
    MutateExperimentArmResult treatmentArm = response.Results.Last();

    Console.WriteLine($"Created control arm with resource name " +
        $"'{controlArm.ResourceName}'.");
    Console.WriteLine($"Created treatment arm with resource name" +
      $" '{treatmentArm.ResourceName}'.");
    return (controlArm, treatmentArm);
}
      

PHP

private static function createExperimentArms(
    GoogleAdsClient $googleAdsClient,
    int $customerId,
    int $campaignId,
    string $experimentResourceName
): string {
    $operations = [];
    $experimentArm1 = new ExperimentArm(
        [
        // The "control" arm references an already-existing campaign.
        'control' => true,
        'campaigns' => [ResourceNames::forCampaign($customerId, $campaignId)],
        'experiment' => $experimentResourceName,
        'name' => 'control arm',
        'traffic_split' => 40
        ]
    );
    $operations[] = new ExperimentArmOperation(['create' => $experimentArm1]);
    $experimentArm2 = new ExperimentArm(
        [
        // The non-"control" arm, also called a "treatment" arm, will automatically
        // generate draft campaigns that you can modify before starting the
        // experiment.
        'control' => false,
        'experiment' => $experimentResourceName,
        'name' => 'experiment arm',
        'traffic_split' => 60
        ]
    );
    $operations[] = new ExperimentArmOperation(['create' => $experimentArm2]);

    // Issues a request to create the experiment arms.
    $experimentArmServiceClient = $googleAdsClient->getExperimentArmServiceClient();
    $response = $experimentArmServiceClient->mutateExperimentArms(
        MutateExperimentArmsRequest::build($customerId, $operations)
            // We want to fetch the draft campaign IDs from the treatment arm, so the easiest
            // way to do that is to have the response return the newly created entities.
            ->setResponseContentType(ResponseContentType::MUTABLE_RESOURCE)
    );
    // Results always return in the order that you specify them in the request.
    // Since we created the treatment arm last, it will be the last result.
    $controlArmResourceName = $response->getResults()[0]->getResourceName();
    $treatmentArm = $response->getResults()[count($operations) - 1];
    print "Created control arm with resource name '$controlArmResourceName'" . PHP_EOL;
    print "Created treatment arm with resource name '{$treatmentArm->getResourceName()}'"
        . PHP_EOL;

    return $treatmentArm->getExperimentArm()->getInDesignCampaigns()[0];
}
      

Python

def create_experiment_arms(
    client: GoogleAdsClient,
    customer_id: str,
    base_campaign_id: str,
    experiment: str,
) -> str:
    """Creates a control and treatment experiment arms.

    Args:
        client: an initialized GoogleAdsClient instance.
        customer_id: a client customer ID.
        base_campaign_id: the campaign ID to associate with the control arm of
          the experiment.
        experiment: the resource name for an experiment.

    Returns:
        the resource name for the new treatment experiment arm.
    """
    operations: List[ExperimentArmOperation] = []

    campaign_service: CampaignServiceClient = client.get_service(
        "CampaignService"
    )

    # The "control" arm references an already-existing campaign.
    operation_1: ExperimentArmOperation = client.get_type(
        "ExperimentArmOperation"
    )
    exa_1: ExperimentArm = operation_1.create
    exa_1.control = True
    exa_1.campaigns.append(
        campaign_service.campaign_path(customer_id, base_campaign_id)
    )
    exa_1.experiment = experiment
    exa_1.name = "control arm"
    exa_1.traffic_split = 40
    operations.append(operation_1)

    # In standard campaign experiments, creating the treatment arm automatically
    # generates a draft campaign that you can modify before starting the experiment.
    operation_2: ExperimentArmOperation = client.get_type(
        "ExperimentArmOperation"
    )
    exa_2: ExperimentArm = operation_2.create
    exa_2.control = False
    exa_2.experiment = experiment
    exa_2.name = "experiment arm"
    exa_2.traffic_split = 60
    operations.append(operation_2)

    experiment_arm_service: ExperimentArmServiceClient = client.get_service(
        "ExperimentArmService"
    )
    request: MutateExperimentArmsRequest = client.get_type(
        "MutateExperimentArmsRequest"
    )
    request.customer_id = customer_id
    request.operations = operations
    # We want to fetch the draft campaign IDs from the treatment arm, so the
    # easiest way to do that is to have the response return the newly created
    # entities.
    request.response_content_type = (
        client.enums.ResponseContentTypeEnum.MUTABLE_RESOURCE
    )
    response: MutateExperimentArmsResponse = (
        experiment_arm_service.mutate_experiment_arms(request=request)
    )

    # Results always return in the order that you specify them in the request.
    # Since we created the treatment arm second, it will be the second result.
    control_arm_result: Any = response.results[0]
    treatment_arm_result: Any = response.results[1]

    print(
        f"Created control arm with resource name {control_arm_result.resource_name}"
    )
    print(
        f"Created treatment arm with resource name {treatment_arm_result.resource_name}"
    )

    return treatment_arm_result.experiment_arm.in_design_campaigns[0]
      

Ruby

def create_experiment_arms(client, customer_id, base_campaign_id, experiment)
  operations = []
  operations << client.operation.create_resource.experiment_arm do |ea|
    # The "control" arm references an already-existing campaign.
    ea.control = true
    ea.campaigns << client.path.campaign(customer_id, base_campaign_id)
    ea.experiment = experiment
    ea.name = 'control arm'
    ea.traffic_split = 40
  end
  operations << client.operation.create_resource.experiment_arm do |ea|
    # The non-"control" arm, also called a "treatment" arm, will automatically
    # generate draft campaigns that you can modify before starting the
    # experiment.
    ea.control = false
    ea.experiment = experiment
    ea.name = 'experiment arm'
    ea.traffic_split = 60
  end

  response = client.service.experiment_arm.mutate_experiment_arms(
    customer_id: customer_id,
    operations: operations,
    # We want to fetch the draft campaign IDs from the treatment arm, so the
    # easiest way to do that is to have the response return the newly created
    # entities.
    response_content_type: :MUTABLE_RESOURCE,
  )

  # Results always return in the order that you specify them in the request.
  # Since we created the treatment arm last, it will be the last result.
  control_arm_result = response.results.first
  treatment_arm_result = response.results.last

  puts "Created control arm with resource name #{control_arm_result.resource_name}."
  puts "Created treatment arm with resource name #{treatment_arm_result.resource_name}."

  treatment_arm_result.experiment_arm.in_design_campaigns.first
end
      

Perl

sub create_experiment_arms {
  my ($api_client, $customer_id, $base_campaign_id, $experiment) = @_;

  my $operations = [];
  push @$operations,
    Google::Ads::GoogleAds::V25::Services::ExperimentArmService::ExperimentArmOperation
    ->new({
      create => Google::Ads::GoogleAds::V25::Resources::ExperimentArm->new({
          # The "control" arm references an already-existing campaign.
          control   => "true",
          campaigns => [
            Google::Ads::GoogleAds::V25::Utils::ResourceNames::campaign(
              $customer_id, $base_campaign_id
            )
          ],
          experiment   => $experiment,
          name         => "control arm",
          trafficSplit => 40
        })});

  push @$operations,
    Google::Ads::GoogleAds::V25::Services::ExperimentArmService::ExperimentArmOperation
    ->new({
      create => Google::Ads::GoogleAds::V25::Resources::ExperimentArm->new({
          # The non-"control" arm, also called a "treatment" arm, will automatically
          # generate draft campaigns that you can modify before starting the
          # experiment.
          control      => "false",
          experiment   => $experiment,
          name         => "experiment arm",
          trafficSplit => 60
        })});

  my $response = $api_client->ExperimentArmService()->mutate({
    customerId => $customer_id,
    operations => $operations,
    # We want to fetch the draft campaign IDs from the treatment arm, so the
    # easiest way to do that is to have the response return the newly created
    # entities.
    responseContentType => MUTABLE_RESOURCE
  });

  # Results always return in the order that you specify them in the request.
  # Since we created the treatment arm last, it will be the last result.
  my $control_arm_result   = $response->{results}[0];
  my $treatment_arm_result = $response->{results}[1];

  printf "Created control arm with resource name '%s'.\n",
    $control_arm_result->{resourceName};
  printf "Created treatment arm with resource name '%s'.\n",
    $treatment_arm_result->{resourceName};
  return $treatment_arm_result->{experimentArm}{inDesignCampaigns}[0];
}
      

curl