Best practice per la mutazione

Per ottimizzare il rendimento, gestire le dipendenze tra le operazioni e gestire le risposte quando modifichi le risorse nell'API Google Ads, attieniti alle seguenti best practice.

Nomi delle risorse temporanei

Sia GoogleAdsService.Mutate sia BatchJobService supportano i nomi delle risorse temporanei a cui è possibile fare riferimento nelle operazioni successive. In questo modo, puoi creare una campagna e i relativi gruppi di annunci, annunci e parole chiave in una singola richiesta di modifica o in un singolo job batch.

Per fare riferimento a una risorsa appena creata all'interno della stessa richiesta di modifica o dello stesso job batch, specifica un ID intero negativo (ad esempio -1 o -2, escluso 0) nel campo resource_name della nuova risorsa. Ad esempio, quando crei una campagna in una richiesta batch, imposta il relativo nome risorsa su customers/CUSTOMER_ID/campaigns/-1. Quando crei un gruppo di annunci in un'operazione successiva all'interno della stessa richiesta, fai riferimento a customers/CUSTOMER_ID/campaigns/-1 come campagna principale. L'API sostituisce automaticamente -1 con l'ID campagna effettivo generato al momento della creazione.

Vincoli di utilizzo

Quando utilizzi i nomi delle risorse temporanei, tieni presente le seguenti regole:

  • L'ordine è importante: puoi fare riferimento a un nome di risorsa temporaneo solo dopo averlo definito. In un elenco di operazioni, l'operazione dipendente (ad esempio la creazione di un gruppo di annunci) deve essere visualizzata dopo l'operazione che crea la risorsa principale (ad esempio la creazione di una campagna).
  • Ambito della richiesta singola o del job batch: i nomi delle risorse temporanei non vengono mantenuti tra job o richieste di modifica separati. Per fare riferimento a una risorsa creata in un job o in una richiesta di modifica precedente, utilizza il relativo nome risorsa effettivo generato dal sistema.
  • Unicità globale: all'interno di un singolo job o di una singola richiesta di modifica, ogni nome di risorsa temporaneo deve utilizzare un intero negativo univoco per tutti i tipi di risorse. Ad esempio, non puoi assegnare -1 sia a una campagna sia a un gruppo di annunci nella stessa richiesta. Il riutilizzo di un ID temporaneo all'interno della stessa richiesta o dello stesso job batch restituisce un errore NewResourceCreationError.DUPLICATE_TEMP_IDS.

Esempio di payload

Supponiamo che tu voglia aggiungere una campagna, un gruppo di annunci e un annuncio in una singola richiesta API o in un singolo job batch. Puoi strutturare l'array mutateOperations in un payload di richiesta GoogleAdsService.Mutate o BatchJobService.AddBatchJobOperations come mostrato nell'esempio JSON REST seguente (con altri campi di risorse obbligatori omessi per brevità):

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

Questo esempio mostra i seguenti dettagli chiave:

  • Il gruppo di annunci utilizza un nuovo ID temporaneo (-2) perché -1 è già assegnato alla campagna.
  • Il gruppo di annunci fa riferimento a customers/CUSTOMER_ID/campaigns/-1 per collegarsi alla campagna creata nell'operazione precedente.
  • adGroupAdOperation fa riferimento a customers/CUSTOMER_ID/adGroups/-2 e omette resourceName perché nessuna operazione successiva nella richiesta fa riferimento al nuovo annuncio.

Raggruppa le operazioni dello stesso tipo

Quando utilizzi GoogleAdsService.Mutate, raggruppa le operazioni per tipo di risorsa nell'array mutate_operations ripetuto rispettando le dipendenze padre e figlio. Questo metodo legge le operazioni in sequenza finché non rileva un tipo di risorsa diverso, quindi raggruppa tutte le operazioni contigue dello stesso tipo in una singola richiesta di servizio di backend.

Ad esempio, se includi 5 operazioni di campagna seguite da 10 operazioni di gruppo di annunci nel campo mutate_operations ripetuto, il sistema esegue due chiamate backend: una a CampaignService per le 5 operazioni di campagna e una seconda a AdGroupService per le 10 operazioni di gruppo di annunci.

Al contrario, l'interleaving delle operazioni ordinandole come [campaign, ad group, campaign, ad group] genera quattro chiamate backend separate. Le chiamate interleaved peggiorano il rendimento dell'API e possono causare timeout delle richieste nei batch di grandi dimensioni.

Gestisci gli errori parziali e i limiti dei batch

Per impostazione predefinita, GoogleAdsService.Mutate esegue il rollback dell'intera richiesta se una singola operazione non va a buon fine. Per eseguire il commit delle operazioni valide anche quando altre operazioni nella stessa richiesta non vanno a buon fine, imposta partial_failure su true su la richiesta ed esamina partial_failure_error nella risposta. Quando partial_failure è true, se un'operazione principale che definisce un ID temporaneo (ad esempio customers/CUSTOMER_ID/campaigns/-1) non supera la convalida, anche tutte le operazioni figlio dipendenti che fanno riferimento a quell'ID temporaneo nella stessa richiesta non vanno a buon fine con NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. Per maggiori dettagli, consulta la guida agli errori parziali.

Tieni presente anche le dimensioni delle richieste, la suddivisione in batch e i limiti di frequenza:

  • Limiti delle dimensioni delle richieste e dei blocchi: una singola GoogleAdsService.Mutate richiesta applica un limite di 10.000 operazioni di modifica (o fino a 20.000 se tutte le operazioni nella richiesta sono AdGroupCriterionOperations, restituendo RequestError.TOO_MANY_MUTATE_OPERATIONS se viene superato) e al massimo 100 operazioni di azione (RequestError.TOO_MANY_ACTION_OPERATIONS). BatchJobService.AddBatchJobOperations applica un massimo di 10.000 operazioni per chiamata, 10.484.504 byte per ogni MutateOperation, e 41.937.920 byte per AddBatchJobOperationsRequest (restituendo BatchJobError.REQUEST_TOO_LARGE se viene superato un limite, con un massimo di 1.000.000 di operazioni totali per job batch). Le modifiche simultanee che hanno come target la stessa campagna o lo stesso account possono attivare errori DatabaseError.CONCURRENT_MODIFICATION.
  • BatchJobService suddivisione in batch atomici: sebbene i job batch vengano eseguiti con semantica di errore parziale (per impostazione predefinita 1000 operazioni per batch secondario interno), BatchJobService raggruppa automaticamente alcune operazioni dipendenti contigue per lo stesso ID principale in batch secondari atomici:
    • Un'operazione AssetGroupOperation (create) seguita da operazioni AssetGroupAssetOperation (create) contigue per lo stesso AssetGroup ID (fino a 1000 operazioni totali, con errore atomico con BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE; ogni AssetGroupOperation update o remove viene eseguita in un batch secondario autonomo a operazione singola).
    • Un'operazione Performance Max CampaignOperation (create, quando le linee guida per il brand sono attivate,ovvero l'impostazione predefinita a meno che brand_guidelines_enabled non sia impostato su false o hotel_property_asset_set non sia impostato) seguita da operazioni CampaignAssetOperation (create) contigue per lo stesso ID Campaign (fino a 1000 operazioni totali, con errore atomico con BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE).
    • Operazioni AssetGroupListingGroupFilterOperation (max 10,000, con errore atomico con BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) o AdGroupCriterionOperation (listing_group, max 20,000, con errore atomico con CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) consecutive per lo stesso elemento principale (AssetGroup o AdGroup).

Recupera gli attributi modificabili dalla risposta

Se imposti il response_content_type della richiesta di modifica su MUTABLE_RESOURCE, la risposta contiene il resource_name e l'oggetto risorsa compilato con i relativi campi modificabili (nonché i campi chiave compilati dal sistema nella risorsa restituita, ad esempio ExperimentArm.in_design_campaigns) per ogni oggetto supportato creato o aggiornato (non rimosso) dalla richiesta. Per le operazioni remove o per i tipi di risorse che non supportano la restituzione di MUTABLE_RESOURCE, la risposta restituisce sempre solo resource_name. Utilizza questa funzionalità per evitare di inviare un ulteriore Search o SearchStream richiesta dopo ogni chiamata di modifica.

Se non imposti response_content_type, l'API Google Ads utilizza per impostazione predefinita RESOURCE_NAME_ONLY e restituisce solo resource_name di ogni risorsa modificata.

L'esempio seguente mostra come recuperare una risorsa modificabile da una chiamata di modifica:

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