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 modifiques 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 modificació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 del 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 principal. 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 la creación de un grupo de anuncios) debe aparecer después de la operación que crea su recurso principal (como la creación de una campaña).
  • Alcance de solicitud única o trabajo por lotes: Los nombres de recursos temporales no persisten entre trabajos separados ni solicitudes de mutación. Para hacer referencia a un recurso creado en un trabajo o una solicitud de modificación anteriores, usa su nombre de recurso real generado por el sistema.
  • Unicidad global: Dentro de 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 se vuelve a usar un ID temporal en la misma solicitud o trabajo por lotes, se devuelve 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 de la API o 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 nuevo ID temporal (-2) porque -1 ya está asignado 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.
  • 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 y respeta las dependencias de elementos principales y secundarios. 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 mutate_operations repetido, el sistema realiza dos llamadas de backend: una a CampaignService para las 5 operaciones de campaña y otra a AdGroupService para las 10 operaciones de grupo de anuncios.

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

Cómo controlar fallas parciales y 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 en 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 principal que define un ID temporal (como customers/CUSTOMER_ID/campaigns/-1) no pasa la validación, las operaciones secundarias dependientes que hacen referencia a ese ID temporal en la misma solicitud también fallarán 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 las solicitudes, los límites de frecuencia y el procesamiento por lotes:

  • Límites de tamaño de solicitudes y fragmentos: Una sola solicitud de 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 AdGroupCriterionOperations, y se devuelve RequestError.TOO_MANY_MUTATE_OPERATIONS si se supera el límite) 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 cada MutateOperation y 41,937,920 bytes por AddBatchJobOperationsRequest (se devuelve BatchJobError.REQUEST_TOO_LARGE si se supera algún límite, con hasta 1,000,000 de operaciones en total por trabajo por lotes). Las mutaciones simultáneas que segmentan la misma campaña o cuenta pueden activar errores de DatabaseError.CONCURRENT_MODIFICATION.
  • BatchJobService Sublotes atómicos: Si bien los trabajos por lotes se ejecutan con semántica de falla parcial (de forma predeterminada, 1,000 operaciones por sublote interno), BatchJobService agrupa automáticamente ciertas operaciones dependientes contiguas para el mismo ID principal 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 sub-lote de una sola operación independiente).
    • Una campaña de máximo rendimiento CampaignOperation (create, cuando se habilitan los lineamientos de la marca, que es la configuració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 de AssetGroupListingGroupFilterOperation (max 10,000, falla de forma atómica con BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) o AdGroupCriterionOperation (listing_group, max 20,000, falla 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 campo response_content_type de tu solicitud de modificación en MUTABLE_RESOURCE, la respuesta contendrá el campo 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 admitido que se haya creado o actualizado (no quitado) por la solicitud. En el caso de las operaciones remove (o 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 Search o SearchStream adicional después de cada llamada de mutación.

Si no estableces response_content_type, la API de Google Ads usará RESOURCE_NAME_ONLY de forma predeterminada y devolverá solo el resource_name de cada recurso mutado.

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.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