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 modifican los 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 modificació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 cuando se crea.

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 superior (como la creación de una campaña).
  • Alcance de solicitud única o trabajo por lotes: Los nombres de recursos temporales no persisten en trabajos separados ni en solicitudes de modificación. Para hacer referencia a un recurso creado en un trabajo o una solicitud de modificación anterior, usa su nombre de recurso real generado por el sistema.
  • Unicidad global: Dentro de un solo trabajo o solicitud de modificació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 dentro de 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 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 operaciones del mismo tipo

Cuando uses GoogleAdsService.Mutate, agrupa las operaciones por tipo de recurso en el array mutate_operations repetido, respetando las dependencias superiores 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 mutate_operations repetido, 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.

Por el contrario, si intercalas operaciones ordenándolas como [campaign, ad group, campaign, ad group], se generan cuatro llamadas de backend independientes. Las llamadas intercaladas degradan el rendimiento de la API y pueden provocar tiempos de espera agotados 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 en la misma solicitud, establece partial_failure en true en la solicitud y examina 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 pasa la validación, cualquier operación secundaria dependiente que haga referencia a ese ID temporal en la misma solicitud también falla 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 división en lotes secundarios y los límites de frecuencia:

  • Límites de tamaño de la solicitud y de los fragmentos: Una sola GoogleAdsService.Mutate solicitud aplica un límite de 10,000 operaciones de modificación (o hasta 20,000 cuando todas las operaciones de la solicitud son AdGroupCriterionOperations, y muestra 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 (muestra 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 modificaciones simultáneas dirigidas a la misma campaña o cuenta pueden activar errores DatabaseError.CONCURRENT_MODIFICATION.
  • BatchJobService división atómica en lotes secundarios: Aunque los trabajos por lotes se ejecutan con semántica de falla parcial (con un valor predeterminado de 1,000 operaciones por lote secundario interno), BatchJobService agrupa automáticamente ciertas operaciones dependientes contiguas para el mismo ID superior en lotes secundarios atómicos:
    • Una AssetGroupOperation (create) seguida de operaciones AssetGroupAssetOperation (create) contiguas para el mismo AssetGroup ID (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 lote secundario independiente de una sola operación).
    • Una campaña de máximo rendimiento CampaignOperation (create, cuando se habilitan los lineamientos de la marca, que es el valor predeterminado, 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 superior (AssetGroup o AdGroup).

Recupera atributos mutables de la respuesta

Si estableces el response_content_type de tu solicitud de modificación en MUTABLE_RESOURCE, la respuesta contiene el resource_name y el objeto de recurso propagado con sus mutables campos (así como los campos clave propagados por el sistema en el recurso que se muestra, como ExperimentArm.in_design_campaigns) para cada objeto admitido 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 Search o SearchStream adicional después de cada llamada de modificación.

Si no estableces response_content_type, la API de Google Ads usa RESOURCE_NAME_ONLY de forma predeterminada 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 modificació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