loams 0.1.0 → 0.1.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 +4 -4
- data/README.md +80 -2
- data/gen/loams-descriptor.binpb +0 -0
- data/lib/loams/app.rb +726 -0
- data/lib/loams/batch.rb +93 -0
- data/lib/loams/builders.rb +622 -0
- data/lib/loams/client.rb +76 -2
- data/lib/loams/consistency.rb +201 -0
- data/lib/loams/descriptors.rb +6 -0
- data/lib/loams/errors.rb +60 -0
- data/lib/loams/invoker.rb +41 -2
- data/lib/loams/subscribe.rb +75 -0
- data/lib/loams/transport.rb +5 -3
- data/lib/loams/values.rb +87 -0
- data/lib/loams/version.rb +1 -1
- data/lib/loams.rb +11 -0
- metadata +7 -1
data/lib/loams/batch.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "loams/errors"
|
|
5
|
+
require "loams/idempotency"
|
|
6
|
+
require "loams/consistency"
|
|
7
|
+
|
|
8
|
+
module Loams
|
|
9
|
+
# What a `write_batch` applied: the merged consistency token (a String, nil
|
|
10
|
+
# when no chunk answered one), the chunks and the rows.
|
|
11
|
+
BatchResult = Struct.new(:token, :chunks, :rows, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
# A `write_batch` stopped at a failed chunk (R13): the chunks before it were
|
|
14
|
+
# applied, and `token` is their merged consistency token.
|
|
15
|
+
class BatchError < LoamsError
|
|
16
|
+
attr_reader :chunks_applied, :rows_applied, :token, :cause_error
|
|
17
|
+
|
|
18
|
+
def initialize(cause, chunks_applied:, rows_applied:, token:)
|
|
19
|
+
@chunks_applied = chunks_applied
|
|
20
|
+
@rows_applied = rows_applied
|
|
21
|
+
@token = token
|
|
22
|
+
@cause_error = cause
|
|
23
|
+
code = cause.respond_to?(:code) ? cause.code : :unknown
|
|
24
|
+
metadata = { "chunks_applied" => chunks_applied.to_s, "rows_applied" => rows_applied.to_s }
|
|
25
|
+
cause_reason = cause.respond_to?(:reason) && cause.reason
|
|
26
|
+
metadata["cause_reason"] = cause_reason.to_s if cause_reason
|
|
27
|
+
super(
|
|
28
|
+
"write_batch stopped at chunk #{chunks_applied + 1}: #{chunks_applied} chunks (#{rows_applied} operations) " \
|
|
29
|
+
"were applied before it; #{cause.message}",
|
|
30
|
+
# SDKP-23: registered under `aborted`; the failed chunk's error is the cause.
|
|
31
|
+
code: :aborted,
|
|
32
|
+
reason: :batch_partially_applied,
|
|
33
|
+
metadata: metadata,
|
|
34
|
+
http_status: cause.respond_to?(:http_status) ? cause.http_status : nil,
|
|
35
|
+
rpc: cause.respond_to?(:rpc) ? cause.rpc : nil,
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# R13: a large write as a sequence of `WriteDocuments` chunks.
|
|
41
|
+
#
|
|
42
|
+
# One chunk is in flight at a time. A chunk closes at +chunk_rows+ ops or
|
|
43
|
+
# when the next op would take it past +chunk_bytes+ of JSON. Each chunk gets
|
|
44
|
+
# its own idempotency key, which the invoker reuses on that chunk's retries,
|
|
45
|
+
# so a retried chunk is applied once. The first chunk that fails stops the
|
|
46
|
+
# batch with a `BatchError`.
|
|
47
|
+
module Batch
|
|
48
|
+
DEFAULT_CHUNK_ROWS = 10_000
|
|
49
|
+
DEFAULT_CHUNK_BYTES = 4 * 1024 * 1024
|
|
50
|
+
|
|
51
|
+
# +ops+ is any Enumerable of `WriteOp` JSON Hashes; +write+ is called with
|
|
52
|
+
# each chunk's ops and its idempotency key and answers the response.
|
|
53
|
+
# Returns a `BatchResult`.
|
|
54
|
+
def self.run(ops, chunk_rows: DEFAULT_CHUNK_ROWS, chunk_bytes: DEFAULT_CHUNK_BYTES, &write)
|
|
55
|
+
raise ArgumentError, "chunk_rows must be positive" unless chunk_rows.positive?
|
|
56
|
+
raise ArgumentError, "chunk_bytes must be positive" unless chunk_bytes.positive?
|
|
57
|
+
|
|
58
|
+
token = nil
|
|
59
|
+
applied = 0
|
|
60
|
+
rows = 0
|
|
61
|
+
chunk = []
|
|
62
|
+
size = 0
|
|
63
|
+
|
|
64
|
+
flush = lambda do
|
|
65
|
+
return if chunk.empty?
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
res = write.call(chunk, Idempotency.uuidv7)
|
|
69
|
+
rescue LoamsError => e
|
|
70
|
+
raise BatchError.new(e, chunks_applied: applied, rows_applied: rows, token: token&.to_s)
|
|
71
|
+
end
|
|
72
|
+
text = res.respond_to?(:token) ? res.token : res["token"]
|
|
73
|
+
if text && !text.empty? && ConsistencyToken.valid?(text)
|
|
74
|
+
parsed = ConsistencyToken.parse(text)
|
|
75
|
+
token = token ? token.merge(parsed) : parsed.normalized
|
|
76
|
+
end
|
|
77
|
+
applied += 1
|
|
78
|
+
rows += chunk.size
|
|
79
|
+
chunk = []
|
|
80
|
+
size = 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ops.each do |op|
|
|
84
|
+
bytes = JSON.generate(op).bytesize
|
|
85
|
+
flush.call if !chunk.empty? && (chunk.size >= chunk_rows || size + bytes > chunk_bytes)
|
|
86
|
+
chunk << op
|
|
87
|
+
size += bytes
|
|
88
|
+
end
|
|
89
|
+
flush.call
|
|
90
|
+
BatchResult.new(token: token&.to_s, chunks: applied, rows: rows)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|