Google Ads API에서 리소스를 변경할 때 성능을 최적화하고, 작업 간 종속 항목을 관리하고, 응답을 처리하려면 다음 권장사항을 따르세요.
임시 리소스 이름
GoogleAdsService.Mutate와
BatchJobService 모두 후속 작업에서 참조할 수 있는 임시 리소스 이름을 지원합니다. 이를 통해 단일 mutate 요청 또는 일괄 작업에서 캠페인과 연결된 광고 그룹, 광고, 키워드를 만들 수 있습니다.
동일한 mutate 요청 또는 일괄 작업 내에서 새로 생성된 리소스를 참조하려면 새 리소스의 resource_name 필드에 음의 정수 ID (예: 0을 제외한 -1 또는 -2)를 지정합니다. 예를 들어 일괄 요청에서 캠페인을 만들 때 리소스 이름을 customers/CUSTOMER_ID/campaigns/-1로 설정합니다.
동일한 요청 내의 후속 작업에서 광고 그룹을 만들 때 customers/CUSTOMER_ID/campaigns/-1을 상위 캠페인으로 참조합니다. API는 생성 시 생성된 실제 캠페인 ID로 -1을 자동으로 바꿉니다.
사용 제약조건
임시 리소스 이름을 사용할 때는 다음 규칙에 유의하세요.
- 순서가 중요함: 임시 리소스 이름은 정의한 후에만 참조할 수 있습니다. 작업 목록에서 종속 작업 (예: 광고 그룹 만들기)은 상위 리소스 (예: 캠페인 만들기)를 만드는 작업 뒤에 표시되어야 합니다.
- 단일 요청 또는 일괄 작업 범위: 임시 리소스 이름은 별도의 작업 또는 mutate 요청 간에 유지되지 않습니다. 이전 작업 또는 mutate 요청에서 생성된 리소스를 참조하려면 시스템에서 생성한 실제 리소스 이름을 사용합니다.
- 전역 고유성: 단일 작업 또는 mutate 요청 내에서 각 임시 리소스 이름은 모든 리소스 유형에서 고유한 음의 정수를 사용해야 합니다.
예를 들어 동일한 요청에서 캠페인과 광고 그룹 모두에
-1을 할당할 수 없습니다. 동일한 요청 또는 일괄 작업 내에서 임시 ID를 다시 사용하면NewResourceCreationError.DUPLICATE_TEMP_IDS오류가 반환됩니다.
페이로드 예시
단일 API 요청 또는 일괄 작업에서 캠페인, 광고 그룹, 광고를 추가한다고 가정해 보겠습니다. 다음 REST JSON 예시와 같이 GoogleAdsService.Mutate 또는 BatchJobService.AddBatchJobOperations 요청 페이로드에서 mutateOperations 배열을 구성할 수 있습니다 (간결성을 위해 다른 필수 리소스 필드는 생략됨).
{
"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"
}
}
}
]
}
이 예시에서는 다음 주요 세부정보를 보여줍니다.
-1이 이미 캠페인에 할당되어 있으므로 광고 그룹은 새 임시 ID (-2)를 사용합니다.- 광고 그룹은
customers/CUSTOMER_ID/campaigns/-1을 참조하여 이전 작업에서 생성된 캠페인에 연결합니다. adGroupAdOperation은customers/CUSTOMER_ID/adGroups/-2를 참조하고 요청의 후속 작업에서 새 광고를 참조하지 않으므로resourceName을 생략합니다.
동일한 유형의 작업 그룹화
GoogleAdsService.Mutate를 사용할 때는 상위
및 하위 종속 항목을 준수하면서 반복되는 mutate_operations 배열에서
리소스 유형별로 작업을 그룹화합니다. 이 메서드는 다른 리소스 유형이 발견될 때까지 작업을 순차적으로 읽은 다음 동일한 유형의 모든 연속 작업을 단일 백엔드 서비스 요청으로 일괄 처리합니다.
예를 들어 반복되는 mutate_operations 필드에 5개의 캠페인 작업과 10개의 광고 그룹
작업을 포함하면 시스템은 두 개의
백엔드 호출을 실행합니다. 하나는 5개의 캠페인
작업에 대한 CampaignService 호출이고 다른 하나는 10개의 광고
그룹 작업에 대한 AdGroupService 호출입니다.
반대로 작업을 [campaign, ad group,
campaign, ad group]으로 정렬하여 작업을 인터리브하면 4개의 별도 백엔드 호출이 발생합니다. 인터리브된 호출은 API 성능을 저하시키고 대규모 일괄 처리에서 요청 시간 초과로 이어질 수 있습니다.
부분적 오류 및 일괄 처리 한도 처리
기본적으로 GoogleAdsService.Mutate는 단일 작업이라도 실패하면 전체
요청을 롤백합니다. 동일한 요청의
다른 작업이 실패하더라도 유효한 작업을 커밋하려면 요청에서 partial_failure를 true로 설정하고 응답에서 partial_failure_error를 검사합니다. partial_failure가 true인 경우 임시 ID를 정의하는 상위 작업(예: customers/CUSTOMER_ID/campaigns/-1)의 유효성 검사에 실패하면 동일한 요청에서 해당 임시 ID를 참조하는 종속 하위 작업도 NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS로 실패합니다. 자세한 내용은
부분적 오류 가이드를 참고하세요.
요청 크기, 하위 일괄 처리, 비율 제한도 염두에 두세요.
- 요청 및 청크 크기 제한: 단일
GoogleAdsService.Mutate요청은 10,000개의 변경 작업 (또는 요청의 모든 작업이AdGroupCriterionOperation인 경우 최대 20,000개, 초과 시RequestError.TOO_MANY_MUTATE_OPERATIONS반환) 및 최대 100개의 작업 작업 (RequestError.TOO_MANY_ACTION_OPERATIONS)을 적용합니다.BatchJobService.AddBatchJobOperations는 호출당 최대 10,000개의 작업, 개별MutateOperation당 10,484,504바이트,AddBatchJobOperationsRequest당 41,937,920바이트를 적용합니다(제한 초과 시BatchJobError.REQUEST_TOO_LARGE반환, 일괄 작업당 총 최대 1,000,000개의 작업). 동일한 캠페인 또는 계정을 타겟팅하는 동시 변경은DatabaseError.CONCURRENT_MODIFICATION오류를 트리거할 수 있습니다. BatchJobService원자적 하위 일괄 처리: 일괄 작업은 부분적 오류 의미 체계 (내부 하위 일괄 처리당 기본값 1,000개의 작업)에 따라 실행되지만BatchJobService는 동일한 상위 ID의 특정 연속 종속 작업을 원자적 하위 일괄 처리로 자동 그룹화합니다.- 동일한
AssetGroupID에 대해 연속되는AssetGroupAssetOperation(create) 작업이 뒤따르는AssetGroupOperation(create)(총 최대 1,000개의 작업,BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE로 원자적으로 실패, 각AssetGroupOperationupdate또는remove는 독립형 단일 작업 하위 일괄 처리에서 실행). - 실적 최대화
CampaignOperation(create, 브랜드 가이드라인 이 사용 설정된 경우,brand_guidelines_enabled이false로 설정되거나hotel_property_asset_set이 설정되지 않는 한 기본값) 뒤에 동일한CampaignID에 대해 연속되는CampaignAssetOperation(create) 작업이 뒤따름 (총 최대 1,000개의 작업,BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE로 원자적으로 실패). - 동일한 상위 항목 (
AssetGroup또는AdGroup)에 대해 연속되는AssetGroupListingGroupFilterOperation(max 10,000,BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE로 원자적으로 실패) 또는AdGroupCriterionOperation(listing_group,max 20,000,CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION로 원자적으로 실패) 작업.
- 동일한
응답에서 변경 가능한 속성 가져오기
변경 요청의 response_content_type을
MUTABLE_RESOURCE으로 설정하면 응답에
resource_name과 요청에서 생성 또는
업데이트(삭제되지 않음)한 모든 지원되는 객체의 변경 가능한 필드(및 반환된 리소스의 주요 시스템 채워진 필드(예:
ExperimentArm.in_design_campaigns))로 채워진 리소스 객체가 포함됩니다. remove 작업 또는 MUTABLE_RESOURCE 반환을 지원하지 않는 리소스 유형의 경우 응답은 항상 resource_name만 반환합니다. 이 기능을 사용하면 각 변경 호출 후에 추가
Search 또는 SearchStream 요청을 보내지 않아도 됩니다.
response_content_type을 설정하지 않으면 Google Ads API는 기본적으로 RESOURCE_NAME_ONLY로 설정되고 변경된 각 리소스의 resource_name만 반환합니다.
다음 예시에서는 변경 호출에서 변경 가능한 리소스를 가져오는 방법을 보여줍니다.
자바
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]; }