Comments (6)
Hi Hendrik,
As long as you don't change the configuration between threads, we expect the library to be thead-safe.
For multiple accounts / auth credentials, we recommend separate instances of AdwordsApi objects, local to thread. Is this the way you are using the library?
-Danial.
from google-api-ads-ruby.
Hi Danial,
the way we use it is as follows:
1 large Merchant Center Account.
~1500 Accounts in the Merchant Center
~15 Other accounts that gave access via the web UI to the merchant center account. Is the Linked Account
the correct term?
We are basically a layer on top of AdWords. That simplifies a few things for the user compared to the AdWords UI.
We have around 5 workers that keep interacting with the AdWords API all the time. They are running in one process, 30 threads in total. A couple of those workers run simultaneously at the same time. So we have lots of AdwordsCampaign
and for each of them we run the worker simultaneously. But each of them interacts with either one of the 1500 merchant center accounts or one of the 15 linked accounts. The crashes occur randomly, not all the time.
The worker that crashed is the following:
# purge_adgroups_without_keywords.rb
class PurgeAdgroupsWithoutKeywords
include Sidekiq::Worker
sidekiq_options retry: 2, backtrace: true
def perform(adwords_campaign_id, test_run = false)
adw = AdwordsCampaign.find adwords_campaign_id
extraction = adw.extraction
user = adw.user
company = adw.company
valid_for_sync = adw.adwords_registered? && extraction.present?
return false if !valid_for_sync
Rails.logger.info "Performing adgroup purge for campaign #{adw.id}"
# Code below
adgroups = adw.adwords.get_adgroups.map{|adgr| adgr[:id]}
# Code below, everything fails here.
adgroups_with_keywords = adw.adwords.get_keywords(include_pending: true, ignore_index_limit: true).keys
adgroups_without_keywords = adgroups - adgroups_with_keywords
Rails.logger.info "Adgroups with keywords for campaign #{adw.id}: #{adgroups_with_keywords.join(' ')}"
Rails.logger.info "Adgroups without keywords for campaign #{adw.id}: #{adgroups_without_keywords.join(' ')}"
if !test_run
# Code below
adw.adwords.delete_adgroups_by_ids(adgroups_without_keywords)
extraction.pages.where(adgroup_id: adgroups_without_keywords).update_all(adgroup_id: nil)
Rails.logger.info "Removed #{adgroups_without_keywords.count} adgroups from AdWords"
end
adgroups_without_keywords
end
end
This is the class we are using to do all the requests with the adwords API. I added the methods that are run below.
# adwords_helper.rb
# Get all adgroups from a campaign
def get_adgroups
@api = get_adwords_api
service = @api.service(:AdGroupService, API_VERSION)
selector = {
:fields => ['Id', 'Name'],
:predicates => [
{:field => 'CampaignId', :operator => 'IN', :values => [self.campaign_id]},
{:field => 'Status', :operator => 'IN', :values => ['ENABLED']},
],
:paging => {
:start_index => 0,
:number_results => 500
}
}
offset, page = 0, {}
result = []
begin
page = service.get(selector)
if page[:entries]
page[:entries].each do |adgroup|
result.push({:id => adgroup[:id], :name => adgroup[:name]})
end
offset += 500
selector[:paging][:start_index] = offset
end
sleep(@@RETRY_INTERVAL)
end while page[:total_num_entries] > offset
result
end
# Get all keywords of adgroups
def get_keywords(limit_to_adgroups: nil, include_pending: false, ignore_index_limit: false)
@api = get_adwords_api
service = @api.service(:AdGroupCriterionService, API_VERSION)
selector = {
:fields => ['Id', 'AdGroupId', 'KeywordText', 'KeywordMatchType', 'CpcBid'],
:predicates => [
{:field => 'CampaignId', :operator => 'IN', :values => [self.campaign_id]},
{:field => 'CriteriaType', :operator => 'IN', :values => ['KEYWORD']},
{:field => 'Status', :operator => 'IN', :values => ['ENABLED']},
{:field => 'ApprovalStatus', :operator => 'IN', :values => (include_pending ? ['APPROVED', 'PENDING_REVIEW', 'UNDER_REVIEW'] : ['APPROVED'])}
],
:paging => {
:start_index => 0,
:number_results => 500
}
}
if limit_to_adgroups
selector[:predicates].push({:field => 'AdGroupId', :operator => 'IN', :values => limit_to_adgroups})
elsif self.adgroup_id
selector[:predicates].push({:field => 'AdGroupId', :operator => 'IN', :values => [self.adgroup_id]})
end
offset, page = 0, {}
result = {}
begin
# Everything fails here.
page = service.get(selector)
if page[:entries]
page[:entries].each do |keyword|
result[keyword[:ad_group_id].to_i] ||= []
result[keyword[:ad_group_id].to_i].push({:id => keyword[:criterion][:id], :text => keyword[:criterion][:text], :match_type => keyword[:criterion][:match_type], :bid => keyword[:bidding_strategy_configuration][:bids].detect{|bid| bid[:bids_type] == 'CpcBid'}[:bid]})
end
offset += 500
selector[:paging][:start_index] = offset
# Maximum start index in this call
if offset > 100000 && !ignore_index_limit
Rails.logger.info "Can't fetch more than 100k keywords for campaign #{self.campaign_id}"
break
end
end
sleep(@@RETRY_INTERVAL)
end while page[:total_num_entries] > offset
result
end
# Deletes specific adgroups
def delete_adgroups_by_ids(ids)
unless perform_dangerous_operations?
return false
end
@api = get_adwords_api
service = @api.service(:MutateJobService, API_VERSION)
operations = ids.map{|id| {
:xsi_type => 'AdGroupOperation',
:operator => 'SET',
:operand => {
:id => id,
:status => 'REMOVED'
}
}}
operations.each_slice(1000) do |ops|
response = service.mutate(ops, {:prerequisite_job_ids => []})
job_id = response[:id]
status = poll_job(service, job_id)
if status != 'COMPLETED'
Rails.logger.fatal "Could not complete deletion of adgroups"
end
sleep(@@RETRY_INTERVAL)
end
end
Just referring to the ticket I posted earlier (sidekiq/sidekiq#2062 sidekiq/sidekiq#2062)
This is the exact stack trace that I received when the segmentation fault occurred.
c:0048 p:---- s:0249 e:000248 CFUNC :initialize
c:0047 p:---- s:0247 e:000246 CFUNC :new
c:0046 p:0030 s:0244 e:000243 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/2.1.0/set.rb:81 [FINISH]
c:0045 p:---- s:0239 e:000238 CFUNC :new
c:0044 p:2070 s:0236 e:000234 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/2.1.0/rexml/parsers/baseparser.rb:379
c:0043 p:0007 s:0204 e:000203 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/2.1.0/rexml/parsers/baseparser.rb:184
c:0042 p:0042 s:0201 e:000200 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/nori-1.1.5/lib/nori/parser/rexml.rb:16
c:0041 p:0027 s:0192 e:000191 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/nori-1.1.5/lib/nori/parser.rb:31
c:0040 p:0036 s:0186 e:000185 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/nori-1.1.5/lib/nori.rb:12
c:0039 p:0030 s:0181 e:000180 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/savon-1.2.0/lib/savon/soap/response.rb:85
c:0038 p:0007 s:0178 e:000177 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/savon-1.2.0/lib/savon/soap/response.rb:63
c:0037 p:0035 s:0175 e:000174 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/google-ads-common-0.9.5/lib/ads_common/results_extractor.rb:39
c:0036 p:0100 s:0167 e:000166 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/google-ads-common-0.9.5/lib/ads_common/savon_service.rb:85
c:0035 p:0013 s:0156 e:000155 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/google-adwords-api-0.13.2/lib/adwords_api/v201406/ad_group_criter
c:0034 p:0300 s:0151 e:000150 METHOD /home/deployer/apps/robot/lib/adwords_helper.rb:693
c:0033 p:0129 s:0139 e:000138 METHOD /home/deployer/apps/robot/app/workers/purge_adgroups_without_keywords.rb:19
c:0032 p:0010 s:0126 e:000125 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/processor.rb:75
c:0031 p:0020 s:0121 e:000120 BLOCK /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/processor.rb:52 [FINISH]
c:0030 p:---- s:0119 e:000118 CFUNC :call
c:0029 p:0016 s:0116 e:000115 LAMBDA /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/middleware/chain.rb:127
c:0028 p:0038 s:0114 e:000113 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidetiq-f3668e03c72c/lib/sidetiq/middleware/history.rb:8
c:0027 p:0030 s:0107 e:000106 LAMBDA /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/middleware/chain.rb:129
c:0026 p:0014 s:0105 e:000104 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-failures-1bddc5c12cea/lib/sidekiq/failures/middle
c:0025 p:0030 s:0097 e:000096 LAMBDA /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/bundler/gems/sidekiq-5047f9220b82/lib/sidekiq/middleware/chain.rb:129
c:0024 p:0024 s:0095 e:000094 METHOD /home/deployer/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/sidekiq-pro-1.9.1/lib/sidekiq/batch/middleware.rb:26
Other things that may be helpful:
- We just upgraded from Rails 3.2.17 to Rails 4.2.17
- We upgraded the
google-adwords-api
to0.13.2
- We are using Ubuntu 14.04 LTS
- The server has 128 GB of RAM
Thanks a bunch for your help!
On 25 Nov 2014, at 11:36, Danial Klimkin <[email protected] mailto:[email protected]> wrote:
Hi Hendrik,
As long as you don't change the configuration between threads, we expect the library to be thead-safe.
For multiple accounts / auth credentials, we recommend separate instances of AdwordsApi objects, local to thread. Is this the way you are using the library?
-Danial.
—
Reply to this email directly or view it on GitHub #38 (comment).
from google-api-ads-ruby.
It looks like you are sharing the same AdwordsApi instance:
@api = get_adwords_api
Try using a separate one per executing thread.
from google-api-ads-ruby.
Hey Danial,
thanks a bunch for the hint. We are already using only 1 instance per thread. So it is created on the fly all the time.
@@config = 'adwords_api.yml'
def initialize(args = {})
self.adwords_id = AdwordsConfig.config[:master_account]
return unless args.is_a?(Hash)
args.each do |k,v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end
def get_adwords_api
@api ||= create_adwords_api
return @api
end
# Creates an instance of AdWords API class. Uses a configuration file and
# Rails config directory.
def create_adwords_api
config_filename = File.join(Rails.root, 'config', @@config)
config = YAML::load_file(config_filename)
if self.adwords_id
config[:authentication][:client_customer_id] = self.adwords_id
end
config[:authentication][:oauth2_token].merge!(self.credentials) if self.credentials
@api = AdwordsApi::Api.new(config)
@api.logger = logger if logger
return @api
end
Do you see anything else that could be an issue? Thanks!
from google-api-ads-ruby.
As far as I can see the version of the gyoku gem used by savon 1.2.0 as used by the adwords gem is not thread safe, see savonrb/gyoku#29.
from google-api-ads-ruby.
Closing this out, as it's a thread safety issue in a dependency.
from google-api-ads-ruby.
Related Issues (20)
- Support for incremental oauth
- ReportableType is unexpected when creating CustomTargetingKey HOT 1
- Creating Callout Extensions in Adwords HOT 1
- Occurred error after entering verification code
- How to perform authentication client side? HOT 2
- Got "Unable to parse response body" from Ruby ad manager api HOT 3
- File permissions issue in google-dfp-api-1.11.0 and google-dfp-api-1.12.0 gems HOT 6
- `download_report_as_stream` ignores HTTP status code HOT 1
- GoogleAdsSavon::SOAP::InvalidResponseError Unable to parse response body HOT 2
- process GoogleAdsService.Search() never ends HOT 1
- Click Performance Report API endpoint failing with a Zlib buffer Error HOT 5
- Support Rails 6.1 HOT 5
- Will this library be sunset with Google AdWords API? HOT 1
- google-dfp-api gem 1.15.0 not compatible with Rails 5.2 HOT 1
- How to install google-adwords-api for version of Ruby lower than 2.2.2 HOT 1
- Default branch is now main
- My dreams officiall pakistan
- Out-Of-Band EOL
- Passing stateful information in initial authorization request? HOT 1
- 2.4.0 uses v202305 API version of Google Ad Manager, not v202308 HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from google-api-ads-ruby.