Prácticas recomendadas para las mutaciones

Sigue estas prácticas recomendadas para optimizar el rendimiento, administrar las dependencias entre las operaciones y controlar las respuestas cuando se mutan recursos en la API de Google Ads.

Nombres de recursos temporales

Tanto GoogleAdsService.Mutate como BatchJobService admiten nombres de recursos temporales a los que se puede hacer referencia en operaciones posteriores. Esto te permite crear una campaña y sus grupos de anuncios, anuncios y palabras clave asociados en una sola solicitud de mutación o trabajo por lotes.

Para hacer referencia a un recurso recién creado dentro de la misma solicitud de mutación o trabajo por lotes, especifica un ID de número entero negativo (como -1 o -2, sin incluir 0) en el campo resource_name del recurso nuevo. Por ejemplo, cuando crees una campaña en una solicitud por lotes, establece su nombre de recurso en customers/CUSTOMER_ID/campaigns/-1. Cuando crees un grupo de anuncios en una operación posterior dentro de la misma solicitud, haz referencia a customers/CUSTOMER_ID/campaigns/-1 como la campaña superior. La API reemplaza automáticamente -1 por el ID de campaña real que se genera en el momento de la creación.

Restricciones de uso

Ten en cuenta las siguientes reglas cuando uses nombres de recursos temporales:

  • El orden es importante: Solo puedes hacer referencia a un nombre de recurso temporal después de definirlo. En una lista de operaciones, la operación dependiente (como crear un grupo de anuncios) debe aparecer después de la operación que crea su recurso superior (como crear una campaña).
  • Alcance de una sola solicitud o de un trabajo por lotes: Los nombres de recursos temporales no persisten en trabajos o solicitudes de mutación independientes. Para hacer referencia a un recurso creado en un trabajo o una solicitud de mutación anteriores, usa su nombre de recurso real generado por el sistema.
  • Unicidad global: En un solo trabajo o solicitud de mutación, cada nombre de recurso temporal debe usar un número entero negativo único en todos los tipos de recursos. Por ejemplo, no puedes asignar -1 a una campaña y a un grupo de anuncios en la misma solicitud. Si vuelves a usar un ID temporal en la misma solicitud o trabajo por lotes, se muestra un error NewResourceCreationError.DUPLICATE_TEMP_IDS.

Ejemplo de carga útil

Supongamos que deseas agregar una campaña, un grupo de anuncios y un anuncio en una sola solicitud a la API o un trabajo por lotes. Puedes estructurar el array mutateOperations en una carga útil de solicitud GoogleAdsService.Mutate o BatchJobService.AddBatchJobOperations, como se muestra en el siguiente ejemplo de JSON de REST (con otros campos de recursos obligatorios omitidos para mayor brevedad):

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

En este ejemplo, se muestran los siguientes detalles clave:

  • El grupo de anuncios usa un ID temporal nuevo (-2) porque ya se asignó -1 a la campaña.
  • El grupo de anuncios hace referencia a customers/CUSTOMER_ID/campaigns/-1 para vincularse a la campaña creada en la operación anterior.
  • El adGroupAdOperation hace referencia a customers/CUSTOMER_ID/adGroups/-2 y omite resourceName porque ninguna operación posterior en la solicitud hace referencia al anuncio nuevo.

Agrupa las operaciones del mismo tipo

Cuando uses GoogleAdsService.Mutate, agrupa las operaciones por tipo de recurso en el array mutate_operations repetido, respetando las dependencias principales y secundarias. Este método lee las operaciones de forma secuencial hasta que encuentra un tipo de recurso diferente y, luego, agrupa todas las operaciones contiguas del mismo tipo en una sola solicitud de servicio de backend.

Por ejemplo, si incluyes 5 operaciones de campaña seguidas de 10 operaciones de grupo de anuncios en el campo repetido mutate_operations, el sistema realiza dos llamadas de backend: una a CampaignService para las 5 operaciones de campaña y una segunda a AdGroupService para las 10 operaciones de grupo de anuncios.

En cambio, intercalar las operaciones ordenándolas como [campaign, ad group, campaign, ad group] genera cuatro llamadas de backend independientes. Las llamadas intercaladas degradan el rendimiento de la API y pueden provocar tiempos de espera de solicitudes en lotes grandes.

Controla las fallas parciales y los límites de lotes

De forma predeterminada, GoogleAdsService.Mutate revierte toda la solicitud si falla alguna operación. Para confirmar operaciones válidas incluso cuando fallan otras operaciones de la misma solicitud, establece partial_failure en true en la solicitud y, luego, inspecciona partial_failure_error en la respuesta. Cuando partial_failure es true, si una operación superior que define un ID temporal (como customers/CUSTOMER_ID/campaigns/-1) no supera la validación, cualquier operación secundaria dependiente que haga referencia a ese ID temporal en la misma solicitud también fallará con NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. Para obtener más detalles, consulta la guía de fallas parciales.

También ten en cuenta el tamaño de la solicitud, la creación de sublotes y los límites de frecuencia:

  • Límites de tamaño de solicitudes y fragmentos: Una sola solicitud GoogleAdsService.Mutate aplica un límite de 10,000 operaciones de mutación (o hasta 20,000 cuando todas las operaciones de la solicitud son AdGroupCriterionOperation, lo que devuelve RequestError.TOO_MANY_MUTATE_OPERATIONS si se supera) y, como máximo, 100 operaciones de acción (RequestError.TOO_MANY_ACTION_OPERATIONS). BatchJobService.AddBatchJobOperations aplica un máximo de 10,000 operaciones por llamada, 10,484,504 bytes por MutateOperation individual y 41,937,920 bytes por AddBatchJobOperationsRequest (lo que devuelve BatchJobError.REQUEST_TOO_LARGE si se supera algún límite, con un máximo de 1,000,000 de operaciones en total por trabajo por lotes). Las mutaciones simultáneas que se orientan a la misma campaña o cuenta pueden activar errores DatabaseError.CONCURRENT_MODIFICATION.
  • Sublotes atómicos de BatchJobService: Aunque los trabajos por lotes se ejecutan con semántica de falla parcial (con un valor predeterminado de 1,000 operaciones por sublote interno), BatchJobService agrupa automáticamente ciertas operaciones dependientes contiguas para el mismo ID superior en sublotes atómicos:
    • Una operación AssetGroupOperation (create) seguida de operaciones AssetGroupAssetOperation (create) contiguas para el mismo ID de AssetGroup (hasta 1,000 operaciones en total, que fallan de forma atómica con BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE; cada AssetGroupOperation update o remove se ejecuta en un sublote independiente de una sola operación).
    • Una operación CampaignOperation de campaña de máximo rendimiento (create, cuando los lineamientos de la marca están habilitados, que es la opción predeterminada, a menos que brand_guidelines_enabled se establezca en false o se establezca hotel_property_asset_set) seguida de operaciones CampaignAssetOperation (create) contiguas para el mismo ID de Campaign (hasta 1,000 operaciones en total, que fallan de forma atómica con BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE).
    • Operaciones consecutivas AssetGroupListingGroupFilterOperation (max 10,000, que fallan de forma atómica con BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) o AdGroupCriterionOperation (listing_group, max 20,000, que fallan de forma atómica con CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) para el mismo elemento superior (AssetGroup o AdGroup).

Recupera atributos mutables de la respuesta

Si configuras el response_content_type de tu solicitud de mutación como MUTABLE_RESOURCE, la respuesta contendrá el resource_name y el objeto de recurso completado con sus campos mutables (así como los campos clave completados por el sistema en el recurso devuelto, como ExperimentArm.in_design_campaigns) para cada objeto compatible creado o actualizado (no quitado) por la solicitud. Para las operaciones remove o para los tipos de recursos que no admiten la devolución de MUTABLE_RESOURCE, la respuesta siempre devuelve solo el resource_name. Usa esta función para evitar enviar una solicitud adicional de Search o SearchStream después de cada llamada de mutación.

Si no configuras response_content_type, la API de Google Ads usa de forma predeterminada RESOURCE_NAME_ONLY y devuelve solo el resource_name de cada recurso modificado.

En el siguiente ejemplo, se muestra cómo recuperar un recurso mutable de una llamada de mutación:

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