Bonnes pratiques concernant les mutations

Suivez ces bonnes pratiques pour optimiser les performances, gérer les dépendances entre les opérations et traiter les réponses lorsque vous modifiez des ressources dans l'API Google Ads.

Noms de ressources temporaires

GoogleAdsService.Mutate et BatchJobService sont compatibles avec les noms de ressources temporaires qui peuvent être référencés dans les opérations suivantes. Cela vous permet de créer une campagne, ainsi que les groupes d'annonces, les annonces et les mots clés associés, dans une seule requête de modification ou un seul job par lot.

Pour référencer une ressource nouvellement créée dans la même requête de modification ou la même tâche par lot, spécifiez un ID entier négatif (tel que -1 ou -2, à l'exclusion de 0) dans le champ resource_name de la nouvelle ressource. Par exemple, lorsque vous créez une campagne dans une requête par lot, définissez son nom de ressource sur customers/CUSTOMER_ID/campaigns/-1. Lorsque vous créez un groupe d'annonces dans une opération ultérieure de la même requête, référencez customers/CUSTOMER_ID/campaigns/-1 comme campagne parente. L'API remplace automatiquement -1 par l'ID de campagne réel généré lors de la création.

Contraintes d'utilisation

Tenez compte des règles suivantes lorsque vous utilisez des noms de ressources temporaires :

  • L'ordre est important : vous ne pouvez référencer un nom de ressource temporaire qu'après l'avoir défini. Dans une liste d'opérations, l'opération dépendante (par exemple, la création d'un groupe d'annonces) doit apparaître après l'opération qui crée sa ressource parente (par exemple, la création d'une campagne).
  • Champ d'application d'une requête unique ou d'un job par lot : les noms de ressources temporaires ne persistent pas entre les tâches distinctes ni les requêtes de modification. Pour référencer une ressource créée dans une tâche ou une requête de modification précédente, utilisez son nom de ressource réel généré par le système.
  • Unicité globale : dans une seule tâche ou requête de modification, chaque nom de ressource temporaire doit utiliser un entier négatif unique pour tous les types de ressources. Par exemple, vous ne pouvez pas attribuer -1 à la fois à une campagne et à un groupe d'annonces dans la même requête. La réutilisation d'un ID temporaire dans la même requête ou la même tâche par lot renvoie une erreur NewResourceCreationError.DUPLICATE_TEMP_IDS.

Exemple de charge utile

Supposons que vous souhaitiez ajouter une campagne, un groupe d'annonces et une annonce dans une seule requête API ou un seul job par lot. Vous pouvez structurer le tableau mutateOperations dans une charge utile de requête GoogleAdsService.Mutate ou BatchJobService.AddBatchJobOperations, comme illustré dans l'exemple JSON REST suivant (les autres champs de ressources obligatoires sont omis par souci de concision) :

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

Cet exemple illustre les principaux détails suivants :

  • Le groupe d'annonces utilise un nouvel ID temporaire (-2), car -1 est déjà attribué à la campagne.
  • Le groupe d'annonces référence customers/CUSTOMER_ID/campaigns/-1 pour se lier à la campagne créée lors de l'opération précédente.
  • adGroupAdOperation référence customers/CUSTOMER_ID/adGroups/-2 et omet resourceName, car aucune opération ultérieure de la requête ne référence la nouvelle annonce.

Regrouper les opérations du même type

Lorsque vous utilisez GoogleAdsService.Mutate, regroupez les opérations par type de ressource dans le tableau mutate_operations répété, tout en respectant les dépendances parent et enfant. Cette méthode lit les opérations de manière séquentielle jusqu'à ce qu'elle rencontre un type de ressource différent, puis regroupe toutes les opérations contiguës du même type dans une seule requête de service de backend.

Par exemple, si vous incluez cinq opérations de campagne suivies de 10 opérations de groupe d'annonces dans le champ mutate_operations répété, le système effectue deux appels de backend : un à CampaignService pour les cinq opérations de campagne et un second à AdGroupService pour les 10 opérations de groupe d'annonces.

En revanche, l'entrelacement des opérations en les ordonnant comme suit : [campaign, ad group, campaign, ad group] entraîne quatre appels de backend distincts. Les appels entrelacés dégradent les performances de l'API et peuvent entraîner des délais d'attente des requêtes sur les lots volumineux.

Gérer les échecs partiels et les limites de lot

Par défaut, GoogleAdsService.Mutate annule l'intégralité de la requête si une seule opération échoue. Pour valider les opérations valides même lorsque d'autres opérations de la même requête échouent, définissez partial_failure sur true dans la requête et inspectez partial_failure_error dans la réponse. Lorsque partial_failure est défini sur true, si une opération parente qui définit un ID temporaire (tel que customers/CUSTOMER_ID/campaigns/-1) échoue à la validation, toutes les opérations enfants dépendantes qui référencent cet ID temporaire dans la même requête échouent également avec NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. Pour en savoir plus, consultez le guide sur les échecs partiels.

Tenez également compte de la taille des requêtes, du sous-traitement par lot et des limites de débit :

  • Limites de taille des requêtes et des blocs : une seule GoogleAdsService.Mutate requête applique une limite de 10 000 opérations de mutation (ou jusqu'à 20 000 lorsque toutes les opérations de la requête sont des AdGroupCriterionOperations, renvoyant RequestError.TOO_MANY_MUTATE_OPERATIONS si cette limite est dépassée) et au maximum 100 opérations d'action (RequestError.TOO_MANY_ACTION_OPERATIONS). BatchJobService.AddBatchJobOperations applique un maximum de 10 000 opérations par appel, 10 484 504 octets par MutateOperation individuelle et 41 937 920 octets par AddBatchJobOperationsRequest (renvoyant BatchJobError.REQUEST_TOO_LARGE si une limite est dépassée, avec un maximum de 1 000 000 d'opérations au total par job par lot). Les mutations simultanées ciblant la même campagne ou le même compte peuvent déclencher des erreurs DatabaseError.CONCURRENT_MODIFICATION.
  • BatchJobService sous-traitement atomique par lot : bien que les jobs par lot s'exécutent selon une sémantique d'échec partiel (par défaut,1 000 opérations par sous-lot interne), BatchJobService regroupe automatiquement certaines opérations dépendantes contiguës pour le même ID parent dans des sous-lots atomiques :
    • Une AssetGroupOperation (create) suivie d'opérations AssetGroupAssetOperation (create) contiguës pour le même AssetGroup ID (jusqu'à 1 000 opérations au total, échec atomique avec BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE ; chaque AssetGroupOperation update ou remove s'exécute dans un sous-lot autonome à une seule opération).
    • Une CampaignOperation Performance Max (create, lorsque les consignes relatives à la marque sont activées, ce qui est le cas par défaut, sauf si brand_guidelines_enabled est défini sur false ou si hotel_property_asset_set est défini) suivie d'opérations CampaignAssetOperation (create) contiguës pour le même ID Campaign (jusqu'à 1 000 opérations au total, échec atomique avec BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE).
    • Opérations AssetGroupListingGroupFilterOperation consécutives (max 10,000, échec atomique avec BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) ou AdGroupCriterionOperation (listing_group, max 20,000, échec atomique avec CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) pour le même parent (AssetGroup ou AdGroup).

Récupérer les attributs modifiables à partir de la réponse

Si vous définissez le response_content_type de votre requête de modification sur MUTABLE_RESOURCE, la réponse contient le resource_name et l'objet de ressource rempli avec ses modifiables champs (ainsi que les champs clés remplis par le système sur la ressource renvoyée, tels que ExperimentArm.in_design_campaigns) pour chaque objet compatible créé ou mis à jour (et non supprimé) par la requête. Pour les opérations remove ou pour les types de ressources qui ne sont pas compatibles avec le renvoi de MUTABLE_RESOURCE, la réponse ne renvoie toujours que le resource_name. Utilisez cette fonctionnalité pour éviter d'envoyer une requête Search ou SearchStream supplémentaire après chaque appel de mutation.

Si vous ne définissez pas response_content_type, l'API Google Ads utilise par défaut RESOURCE_NAME_ONLY et ne renvoie que le resource_name de chaque ressource mutée.

L'exemple suivant montre comment récupérer une ressource modifiable à partir d'un appel de mutation :

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