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 modificar recursos na API Google Ads.

Nomes de recursos temporários

Tanto GoogleAdsService.Mutate quanto 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 job em lote, especifique um ID de número inteiro negativo (como -1 ou -2, exceto 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, use customers/CUSTOMER_ID/campaigns/-1 como a campanha principal. A API substitui automaticamente -1 pelo ID da campanha gerado na criação.

Restrições de uso

Lembre-se das seguintes regras ao usar nomes de recursos temporários:

  • A ordem é importante: só é possível referenciar 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 pai (como criar uma campanha).
  • Escopo de solicitação única ou job em lote: os nomes de recursos temporários não persistem em jobs ou solicitações de mutação separados. Para fazer referência a um recurso criado em um job ou uma solicitação de mutação anterior, use o nome de recurso real gerado pelo sistema.
  • Exclusividade 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 job 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. É possível estruturar a matriz mutateOperations em um payload de solicitação GoogleAdsService.Mutate ou BatchJobService.AddBatchJobOperations, conforme mostrado no exemplo de JSON REST a seguir (com outros campos de recursos obrigatórios omitidos para fins de 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 importantes:

  • 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 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 5 operações de campanha seguidas por 10 operações de grupo de anúncios no campo repetido mutate_operations, o sistema vai realizar duas chamadas de back-end: uma para CampaignService para as 5 operações de campanha e outra para AdGroupService para as 10 operações de grupo de anúncios.

Por outro lado, intercalar as operações ordenando-as como [campaign, ad group, campaign, ad group] resulta em quatro chamadas de back-end separadas. As chamadas intercaladas degradam o desempenho da API e podem levar a tempos limite de solicitação em lotes grandes.

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 falharem, defina partial_failure como true na solicitação e inspecione partial_failure_error na resposta. Quando partial_failure é true, se uma operação pai que define um ID temporário (como customers/CUSTOMER_ID/campaigns/-1) falhar na validação, todas as operações filhas dependentes que fizerem 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 sublote e os limites de taxa:

  • Limites de tamanho de solicitação e de bloco: uma única solicitação GoogleAdsService.Mutate aplica 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 aplica 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.
  • Sublote atômico de BatchJobService: embora os jobs em lote sejam executados com semântica de falha parcial (o padrão é 1.000 operações por sublote interno), o BatchJobService agrupa automaticamente determinadas operações dependentes contíguas para o mesmo ID pai em sublotes atômicos:
    • Um AssetGroupOperation (create) seguido por operações AssetGroupAssetOperation (create) contíguas 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 operação CampaignOperation de Performance Max (create, quando as diretrizes da 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 de 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, com falha atômica com BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) ou AdGroupCriterionOperation (listing_group, max 20,000, com falha atômica com CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) para o mesmo 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 são compatíveis com 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 como padrão e retornar apenas o resource_name de cada recurso modificado.

O exemplo a seguir demonstra como recuperar 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.V26.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|
    # In standard campaign experiments, creating the treatment arm automatically
    # generates a draft campaign 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 second, it will be the second 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::V26::Services::ExperimentArmService::ExperimentArmOperation
    ->new({
      create => Google::Ads::GoogleAds::V26::Resources::ExperimentArm->new({
          # The "control" arm references an already-existing campaign.
          control   => "true",
          campaigns => [
            Google::Ads::GoogleAds::V26::Utils::ResourceNames::campaign(
              $customer_id, $base_campaign_id
            )
          ],
          experiment   => $experiment,
          name         => "control arm",
          trafficSplit => 40
        })});

  push @$operations,
    Google::Ads::GoogleAds::V26::Services::ExperimentArmService::ExperimentArmOperation
    ->new({
      create => Google::Ads::GoogleAds::V26::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