olaf 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d45d092623bf97e4a23624cfcc145afd281dcb91011bee43e9086e35f1af9ff
4
- data.tar.gz: 54ae40103a4ed018b04be956f86ad9316a2239ab719e721e33eb9d79a11e1b2c
3
+ metadata.gz: dd476121e33e97e071c41dc3e75a8128eddee118c93f0ec93acd61fa167b0523
4
+ data.tar.gz: 843da2abba59ade1dc644b47a4575abf77102ed7a58dd0ce1c45e6f7ba6f7968
5
5
  SHA512:
6
- metadata.gz: f874ca55bef0c740c90084a7db2e0487668a55c4af33e6d8879c798677aa4921eb0aa1c1f146d0af54c2de854b61c783c0a70e362b31c2cc5baf8cbee8ab37d4
7
- data.tar.gz: 97331d8aa92d9b735b45c7931a08bccc49db2e97caee1ab11c1b0f19c067b63192a29625c356a90d5d85eee8f1f59ed924c4712d71ba5bbfbe581a058a911cc2
6
+ metadata.gz: 577d617e9e02f3bbaa858425e70e6d87803af2a9d2764626d30e6d0fda0bce130171b33566e642354e30990ffe19d7e9b4b8c55f48f29d63712a1e81743eb31c
7
+ data.tar.gz: 39da006e922a15ad3fa88a7c7c45979aff8eeb8e13b6e4c5340ee52608f54cf4930b4314e4925c2103ed7559604f664fbb18f2dd8084903605075e1d9f908293
data/README.md CHANGED
@@ -88,7 +88,8 @@ Olaf.instance.register_result(FetchUsers, [{ id: 42 }], with: { department_id: 1
88
88
  ```
89
89
 
90
90
  Whichever driver runs the query, a failure it reports is wrapped into
91
- `Olaf::QueryExecutionError`, carrying the query metadata.
91
+ `Olaf::QueryExecutionError`, carrying the query metadata. A BigQuery connection
92
+ that keeps dropping can be the exception: see below.
92
93
 
93
94
  ## BigQuery
94
95
 
@@ -97,7 +98,8 @@ Olaf::BigQuery.new(
97
98
  project: 'carwow',
98
99
  credentials: JSON.parse(ENV['BIGQUERY_CREDENTIALS']), # ambient Google credentials when omitted
99
100
  maximum_bytes_billed: 5 * Olaf::BigQuery::GIGABYTE, # default: 1 GiB
100
- labels: { service: 'flatmin', country: 'uk' }
101
+ labels: { service: 'flatmin', country: 'uk' },
102
+ retry_delays: [1, 2] # default: retry twice, after 1s then 2s
101
103
  )
102
104
  ```
103
105
 
@@ -114,6 +116,17 @@ The query runs as a job, so that it can be capped and labelled:
114
116
  ```ruby
115
117
  driver :big_query, maximum_bytes_billed: 10 * Olaf::BigQuery::GIGABYTE
116
118
  ```
119
+ * A call whose connection drops mid-request is retried once after each of the
120
+ `retry_delays`. Waiting for the job is retried on its own, so a dropped status
121
+ check doesn't start a second job. A drop arrives in one of two forms, and both
122
+ are retried:
123
+ * A bare `OpenSSL::SSL::SSLError`, which is how google-apis-core 0.x reports
124
+ an SSL read that ends early. Once the retries run out it is raised as it is,
125
+ not wrapped, so a job runner such as Sidekiq can retry it in turn.
126
+ * A `Google::Cloud::Error` caused by a `Google::Apis::TransmissionError`.
127
+ google-apis-core 1.x reports every dropped connection this way, and 0.x
128
+ does the same for a reset connection. Once the retries run out it is wrapped
129
+ into `Olaf::QueryExecutionError`, like any other error the API reports.
117
130
 
118
131
  `Olaf.execute` returns the rows. `Olaf.instance(:big_query).run(query)` returns
119
132
  the finished job instead, for its statistics:
@@ -1,5 +1,6 @@
1
1
  # Not a dependency of the gem: consumers of this driver add it themselves.
2
2
  require 'google/cloud/bigquery'
3
+ require 'securerandom'
3
4
 
4
5
  # The API client debug-logs whole response bodies, which for a warehouse query
5
6
  # is the entire result set.
@@ -17,14 +18,17 @@ module Olaf
17
18
  LABEL_FORMAT = /[^a-z0-9_-]/.freeze
18
19
  LABEL_MAX_LENGTH = 63
19
20
 
21
+ RETRY_DELAYS = [1, 2].freeze
22
+
20
23
  # Credentials default to the ambient Google ones, and the client is built
21
24
  # from the arguments unless one is given.
22
25
  def initialize(project: nil, credentials: nil, maximum_bytes_billed: DEFAULT_MAXIMUM_BYTES_BILLED,
23
- labels: {}, client: nil)
26
+ labels: {}, retry_delays: RETRY_DELAYS, client: nil)
24
27
  @project = project
25
28
  @credentials = credentials
26
29
  @maximum_bytes_billed = maximum_bytes_billed
27
30
  @labels = labels
31
+ @retry_delays = retry_delays
28
32
  @client = client
29
33
  end
30
34
 
@@ -35,15 +39,11 @@ module Olaf
35
39
  # Returns the finished job, so callers can read its stats (bytes billed,
36
40
  # cache hit) on top of its rows.
37
41
  #
38
- # @raises Olaf::QueryExecutionError
42
+ # @raises Olaf::QueryExecutionError, or the OpenSSL::SSL::SSLError of a
43
+ # connection that keeps dropping, which google-apis-core 0.x doesn't wrap
39
44
  def run(olaf_query)
40
- job = client.query_job(
41
- sql(olaf_query),
42
- params: params(olaf_query),
43
- maximum_bytes_billed: maximum_bytes_billed(olaf_query),
44
- labels: labels(olaf_query)
45
- )
46
- job.wait_until_done!
45
+ job = start_job(olaf_query)
46
+ retrying_dropped_connections { job.wait_until_done! }
47
47
 
48
48
  raise QueryExecutionError.new(job.error.to_h['message'].to_s, olaf_query) if job.failed?
49
49
 
@@ -58,9 +58,51 @@ module Olaf
58
58
  @client ||= Google::Cloud::Bigquery.new(project: @project, credentials: @credentials)
59
59
  end
60
60
 
61
+ # A connection can drop after BigQuery has started the job, so every attempt
62
+ # reuses one job ID: BigQuery refuses the retry as a duplicate, and the job
63
+ # already started is loaded rather than running and billing the query twice.
64
+ # If the lookup can't find the job, as happens for one in a single region,
65
+ # the refusal is re-raised.
66
+ def start_job(olaf_query)
67
+ job_id = "job_#{SecureRandom.urlsafe_base64(21)}"
68
+
69
+ retrying_dropped_connections do
70
+ client.query_job(
71
+ sql(olaf_query),
72
+ params: params(olaf_query),
73
+ maximum_bytes_billed: maximum_bytes_billed(olaf_query),
74
+ labels: labels(olaf_query),
75
+ job_id:
76
+ )
77
+ rescue Google::Cloud::AlreadyExistsError
78
+ client.job(job_id) || raise
79
+ end
80
+ end
81
+
82
+ # google-cloud-bigquery switches off google-apis-core's retries.
83
+ def retrying_dropped_connections
84
+ @retry_delays.each do |delay|
85
+ return yield
86
+ rescue StandardError => error
87
+ raise unless dropped_connection?(error)
88
+
89
+ sleep delay
90
+ end
91
+
92
+ yield
93
+ end
94
+
95
+ # google-apis-core 1.x reports every dropped connection as a TransmissionError,
96
+ # and 0.x does the same for a reset connection; google-cloud-bigquery wraps it.
97
+ def dropped_connection?(error)
98
+ error.is_a?(OpenSSL::SSL::SSLError) ||
99
+ (error.is_a?(Google::Cloud::Error) && error.cause.is_a?(Google::Apis::TransmissionError))
100
+ end
101
+
61
102
  # Google::Cloud::Error covers everything the API reports, Google::Auth::Error
62
103
  # credentials missing or expired before a call is made. The second one only
63
- # exists in googleauth 1.14 and later.
104
+ # exists in googleauth 1.14 and later. A bare OpenSSL::SSL::SSLError is left
105
+ # out, so once its retries run out the caller's job runner can still retry it.
64
106
  def wrapped_errors
65
107
  @wrapped_errors ||= [
66
108
  Google::Cloud::Error,
data/olaf.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'olaf'
3
- s.version = '1.0.0'
3
+ s.version = '1.0.1'
4
4
  s.date = Time.now.strftime('%Y-%m-%d')
5
5
  s.summary = 'Ruby wrapper for warehouse queries.'
6
6
  s.authors = ['Emiliano Mancuso']
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: olaf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emiliano Mancuso
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-10-09 00:00:00.000000000 Z
10
+ date: 2026-10-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: google-cloud-bigquery
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  - !ruby/object:Gem::Version
76
76
  version: '0'
77
77
  requirements: []
78
- rubygems_version: 4.0.20
78
+ rubygems_version: 3.6.9
79
79
  specification_version: 4
80
80
  summary: Ruby wrapper for warehouse queries.
81
81
  test_files: