Práticas recomendadas para mutações

Siga estas práticas recomendadas para otimizar a performance, gerenciar dependências entre operações e processar respostas ao fazer mutações de recursos na API Google Ads.

Nomes de recursos temporários

GoogleAdsService.Mutate e BatchJobService são compatíveis com nomes de recursos temporários que podem ser referenciados em operações subsequentes. Isso permite criar uma campanha e os grupos de anúncios, anúncios e palavras-chave associados em uma única solicitação de mutação ou job em lote.

Para referenciar um recurso recém-criado na mesma solicitação de mutação ou trabalho em lote, especifique um ID inteiro negativo (como -1 ou -2, excluindo 0) no campo resource_name do novo recurso. Por exemplo, ao criar uma campanha em uma solicitação em lote, defina o nome do recurso como customers/CUSTOMER_ID/campaigns/-1. Ao criar um grupo de anúncios em uma operação posterior na mesma solicitação, faça referência a customers/CUSTOMER_ID/campaigns/-1 como a campanha principal. A API substitui automaticamente -1 pelo ID da campanha real gerado na criação.

Restrições de uso

Considere as seguintes regras ao usar nomes de recursos temporários:

  • A ordem é importante:só é possível fazer referência a um nome de recurso temporário depois de defini-lo. Em uma lista de operações, a operação dependente (como criar um grupo de anúncios) precisa aparecer depois da operação que cria o recurso principal (como criar uma campanha).
  • Escopo de solicitação única ou job em lote:os nomes de recursos temporários não persistem em jobs separados ou solicitações de mutação. Para fazer referência a um recurso criado em um job ou solicitação de mutação anterior, use o nome de recurso real gerado pelo sistema.
  • Unicidade global:em um único job ou solicitação de mutação, cada nome de recurso temporário precisa usar um número inteiro negativo exclusivo em todos os tipos de recursos. Por exemplo, não é possível atribuir -1 a uma campanha e a um grupo de anúncios na mesma solicitação. Reutilizar um ID temporário na mesma solicitação ou trabalho em lote retorna um erro NewResourceCreationError.DUPLICATE_TEMP_IDS.

Exemplo de payload

Suponha que você queira adicionar uma campanha, um grupo de anúncios e um anúncio em uma única solicitação de API ou job em lote. Você pode estruturar a matriz mutateOperations em um payload de solicitação GoogleAdsService.Mutate ou BatchJobService.AddBatchJobOperations, conforme mostrado no exemplo JSON REST a seguir (com outros campos de recursos obrigatórios omitidos para brevidade):

{
  "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"
        }
      }
    }
  ]
}

Este exemplo demonstra os seguintes detalhes principais:

  • O grupo de anúncios usa um novo ID temporário (-2) porque -1 já está atribuído à campanha.
  • O grupo de anúncios faz referência a customers/CUSTOMER_ID/campaigns/-1 para se vincular à campanha criada na operação anterior.
  • O adGroupAdOperation faz referência a customers/CUSTOMER_ID/adGroups/-2 e omite resourceName porque nenhuma operação subsequente na solicitação faz referência ao novo anúncio.

Agrupar operações do mesmo tipo

Ao usar GoogleAdsService.Mutate, agrupe as operações por tipo de recurso na matriz mutate_operations repetida, respeitando as dependências de pai e filho. Esse método lê as operações sequencialmente até encontrar um tipo de recurso diferente e, em seguida, agrupa todas as operações contíguas do mesmo tipo em uma única solicitação de serviço de back-end.

Por exemplo, se você incluir cinco operações de campanha seguidas por dez operações de grupo de anúncios no campo repetido mutate_operations, o sistema fará duas chamadas de back-end: uma para CampaignService nas cinco operações de campanha e outra para AdGroupService nas dez operações de grupo de anúncios.

Em contraste, intercalar operações ordenando-as como [campaign, ad group, campaign, ad group] resulta em quatro chamadas de back-end separadas. Chamadas intercaladas reduzem a performance da API e podem causar tempos limite de solicitação em grandes lotes.

Processar falhas parciais e limites de lote

Por padrão, o GoogleAdsService.Mutate reverte toda a solicitação se uma única operação falhar. Para confirmar operações válidas mesmo quando outras operações na mesma solicitação falham, defina partial_failure como true na solicitação e inspecione partial_failure_error na resposta. Quando partial_failure é true, se uma operação principal que define um ID temporário (como customers/CUSTOMER_ID/campaigns/-1) falhar na validação, todas as operações filhas dependentes que fazem referência a esse ID temporário na mesma solicitação também vão falhar com NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. Para mais detalhes, consulte o guia de falha parcial.

Também considere o tamanho da solicitação, o subagrupamento em lotes e os limites de taxa:

  • Limites de tamanho de solicitação e bloco:uma única solicitação GoogleAdsService.Mutate impõe um limite de 10.000 operações de mutação (ou até 20.000 quando todas as operações na solicitação são AdGroupCriterionOperations, retornando RequestError.TOO_MANY_MUTATE_OPERATIONS se excedido) e no máximo 100 operações de ação (RequestError.TOO_MANY_ACTION_OPERATIONS). BatchJobService.AddBatchJobOperations impõe um máximo de 10.000 operações por chamada, 10.484.504 bytes por MutateOperation individual e 41.937.920 bytes por AddBatchJobOperationsRequest (retornando BatchJobError.REQUEST_TOO_LARGE se algum limite for excedido, com até 1.000.000 de operações no total por job em lote). Mutações simultâneas que segmentam a mesma campanha ou conta podem acionar erros DatabaseError.CONCURRENT_MODIFICATION.
  • Sublotes atômicos BatchJobService:embora os jobs em lote sejam executados com semântica de falha parcial (padrão de 1.000 operações por sublote interno), o BatchJobService agrupa automaticamente determinadas operações contíguas dependentes do mesmo ID principal em sublotes atômicos:
    • Um AssetGroupOperation (create) seguido por operações contíguas de AssetGroupAssetOperation (create) para o mesmo ID de AssetGroup (até 1.000 operações no total, falhando atomicamente com BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE; cada AssetGroupOperation update ou remove é executado em um sublote independente de operação única).
    • Uma campanha Performance Max CampaignOperation (create, quando as diretrizes de marca estão ativadas, o que é o padrão, a menos que brand_guidelines_enabled esteja definido como false ou hotel_property_asset_set esteja definido) seguida por operações CampaignAssetOperation (create) contíguas para o mesmo ID Campaign (até 1.000 operações no total, falhando atomicamente com BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE).
    • Operações consecutivas de AssetGroupListingGroupFilterOperation (max 10,000, falhando atomicamente com BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) ou AdGroupCriterionOperation (listing_group, max 20,000, falhando atomicamente com CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) para o mesmo elemento pai (AssetGroup ou AdGroup).

Recuperar atributos mutáveis da resposta

Se você definir o response_content_type da sua solicitação de mutação como MUTABLE_RESOURCE, a resposta vai conter o resource_name e o objeto de recurso preenchido com os campos mutáveis (além dos campos principais preenchidos pelo sistema no recurso retornado, como ExperimentArm.in_design_campaigns) para cada objeto compatível criado ou atualizado (não removido) pela solicitação. Para operações remove ou para tipos de recursos que não aceitam o retorno de MUTABLE_RESOURCE, a resposta sempre retorna apenas o resource_name. Use esse recurso para evitar o envio de uma solicitação Search ou SearchStream adicional após cada chamada de mutação.

Se você não definir response_content_type, a API Google Ads vai usar RESOURCE_NAME_ONLY por padrão e retornar apenas o resource_name de cada recurso mutado.

O exemplo a seguir mostra como extrair um recurso mutável de uma chamada de mutação:

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