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
-1sia 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 erroreNewResourceCreationError.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/-1per collegarsi alla campagna creata nell'operazione precedente. adGroupAdOperationfa riferimento acustomers/CUSTOMER_ID/adGroups/-2e ometteresourceNameperché 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.Mutateimpone un limite di 10.000 operazioni di modifica (o fino a 20.000 se tutte le operazioni nella richiesta sonoAdGroupCriterionOperation, restituendoRequestError.TOO_MANY_MUTATE_OPERATIONSse superato) e al massimo 100 operazioni di azione (RequestError.TOO_MANY_ACTION_OPERATIONS).BatchJobService.AddBatchJobOperationsimpone un massimo di 10.000 operazioni per chiamata, 10.484.504 byte per ogniMutateOperatione 41.937.920 byte perAddBatchJobOperationsRequest(restituendoBatchJobError.REQUEST_TOO_LARGEse 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 erroriDatabaseError.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),BatchJobServiceraggruppa automaticamente determinate operazioni contigue dipendenti per lo stesso ID principale in sub-batch atomici: - Un
AssetGroupOperation(create) seguito da operazioniAssetGroupAssetOperation(create) contigue per lo stesso IDAssetGroup(fino a 1000 operazioni totali, con esito negativo in modo atomico conBatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE; ogniAssetGroupOperationupdateoremoveviene 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 chebrand_guidelines_enablednon sia impostato sufalseohotel_property_asset_setnon sia impostato) seguita da operazioniCampaignAssetOperation(create) contigue per lo stesso IDCampaign(fino a 1000 operazioni totali, con esito negativo in modo atomico conBatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE). - Operazioni consecutive
AssetGroupListingGroupFilterOperation(max 10,000, non riuscite in modo atomico conBatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) oAdGroupCriterionOperation(listing_group,max 20,000, non riuscite in modo atomico conCriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) per lo stesso elemento principale (AssetGroupoAdGroup).
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]; }