Best practice per la mutazione

Segui queste best practice per ottimizzare il rendimento, gestire le dipendenze tra le operazioni e gestire le risposte durante la mutazione delle risorse nell'API Google Ads.

Nomi delle risorse temporanei

Sia GoogleAdsService.Mutate che BatchJobService supportano nomi di 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 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 nome della 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 di una singola richiesta o di un job batch:i nomi delle risorse temporanee non vengono mantenuti tra job separati o richieste di mutazione. Per fare riferimento a una risorsa creata in un job o in una richiesta di modifica precedente, utilizza il nome della risorsa effettivo generato dal sistema.
  • Unicità globale:all'interno di un singolo job o di una singola richiesta di modifica, ogni nome di risorsa temporanea deve utilizzare un numero intero negativo univoco in 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 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 nel seguente esempio JSON REST (con altri campi delle 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 operazioni dello stesso tipo

Quando utilizzi GoogleAdsService.Mutate, raggruppa le operazioni per tipo di risorsa nell'array mutate_operations ripetuto rispettando le dipendenze principali e secondarie. Questo metodo legge in sequenza le operazioni finché non incontra 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 sulla campagna seguite da 10 operazioni sul gruppo di annunci nel campo mutate_operations ripetuto, il sistema esegue due chiamate di backend: una a CampaignService per le 5 operazioni sulla campagna e una seconda a AdGroupService per le 10 operazioni sul gruppo di annunci.

Al contrario, l'interleaving delle operazioni ordinandole come [campaign, ad group, campaign, ad group] comporta quattro chiamate di backend separate. Le chiamate interleaved riducono le prestazioni dell'API e possono causare timeout delle richieste per batch di grandi dimensioni.

Gestire errori parziali e limiti 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 di operazioni valide anche quando altre operazioni nella stessa richiesta non vanno a buon fine, imposta partial_failure su true nella richiesta e controlla partial_failure_error nella risposta. Quando partial_failure è true, se la convalida di un'operazione principale che definisce un ID temporaneo (ad esempio customers/CUSTOMER_ID/campaigns/-1) non va a buon fine, anche le operazioni secondarie 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 all'errore parziale.

Tieni inoltre presente le dimensioni delle richieste, i sottobatch e i limiti di frequenza:

  • Limiti di dimensioni di richieste e blocchi: una singola richiesta GoogleAdsService.Mutate impone un limite di 10.000 operazioni di modifica (o fino a 20.000 se tutte le operazioni nella richiesta sono AdGroupCriterionOperation, restituendo RequestError.TOO_MANY_MUTATE_OPERATIONS se superato) e al massimo 100 operazioni di azione (RequestError.TOO_MANY_ACTION_OPERATIONS). BatchJobService.AddBatchJobOperations impone 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.
  • Suddivisione in batch atomici di BatchJobService:sebbene i job batch vengano eseguiti in base alla semantica di errore parziale (con un valore predefinito di 1000 operazioni per sub-batch interno), BatchJobService raggruppa automaticamente determinate operazioni contigue dipendenti per lo stesso ID principale in sub-batch atomici:
    • Un AssetGroupOperation (create) seguito da operazioni AssetGroupAssetOperation (create) contigue per lo stesso ID AssetGroup (fino a 1000 operazioni totali, con esito negativo in modo atomico con BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE; ogni AssetGroupOperation update o remove viene eseguito in un sub-batch autonomo a singola operazione).
    • Un'operazione Performance Max CampaignOperation (create, quando le linee guida per il brand sono attive,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 esito negativo in modo atomico con BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE).
    • Operazioni consecutive AssetGroupListingGroupFilterOperation (max 10,000, non riuscite in modo atomico con BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) o AdGroupCriterionOperation (listing_group, max 20,000, non riuscite in modo atomico con CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) per lo stesso elemento principale (AssetGroup o AdGroup).

Recuperare gli attributi modificabili dalla risposta

Se imposti response_content_type della richiesta di modifica su MUTABLE_RESOURCE, la risposta contiene 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 una richiesta Search o SearchStream aggiuntiva dopo ogni chiamata mutate.

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 mutate:

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