emb 0.2.1 → 0.2.3
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 +177 -2
- data/lib/emb/batch.rb +63 -0
- data/lib/emb/client.rb +32 -12
- data/lib/emb/configuration.rb +30 -0
- data/lib/emb/middleware.rb +20 -0
- data/lib/emb/proxy.rb +2 -0
- data/lib/emb.rb +13 -0
- metadata +19 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2210ac3c666538429c3d0eccc58c26b94bc87a26f65cd6c3b4ba2f754b5de6fa
|
|
4
|
+
data.tar.gz: f576b3edf8bec7d38d156b35082e86ac19a80324c0f437e78197f674f0156215
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3dc881cd39e7dcdf6898afa215d9d852a638a2df020d225056e861b9f79e57f13001a30c0e88b0b1cf0e997328c9682b6d4f2cdfbd97c7795e1adc6a8262a279
|
|
7
|
+
data.tar.gz: 93aa184f12078697b8f27946567dd3221b99d098f67f207d4cbe3842472583ae76fba8a8aa8bab5a5cba3a432c12eff2a2449d53ff93187a043a6bfd0e864c4d
|
data/README.md
CHANGED
|
@@ -44,12 +44,49 @@ Emb.setup
|
|
|
44
44
|
2. `EMB_URL` environment variable
|
|
45
45
|
3. Default: `redis://localhost:6379`
|
|
46
46
|
|
|
47
|
+
### Global configuration
|
|
48
|
+
|
|
49
|
+
Every client (`Emb.setup`, `Emb.new`, and the lazily-created default client) inherits a
|
|
50
|
+
shared global `Emb::Config` value object set with `Emb.configure`. Settings
|
|
51
|
+
resolve in this order — the first one wins:
|
|
52
|
+
|
|
53
|
+
1. **Explicit per-call option** (`Emb.setup(pool: 10)`)
|
|
54
|
+
2. **`Emb.configure` value**
|
|
55
|
+
3. **Built-in default**
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
Emb.configure do |c|
|
|
59
|
+
c.pool = 8
|
|
60
|
+
c.batch = false # opt out of lazy batching app-wide
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
Emb.configuration # => the shared Emb::Configuration
|
|
64
|
+
Emb::Client.new(pool: 20) # per-call still wins
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`EMB_URL` remains the only environment variable (connection URL fallback, as before);
|
|
68
|
+
`Emb.configure { |c| c.url = ... }` or an explicit `url:` override it.
|
|
69
|
+
|
|
70
|
+
### Out-of-the-box defaults
|
|
71
|
+
|
|
72
|
+
The shipped defaults are benchmark-derived (see `BENCHMARK.md`): **lazy batching is on by
|
|
73
|
+
default** (`batch: true` — each embed coalesces into one `EMB.MULTI`), pool `5`, pure-Ruby
|
|
74
|
+
RESP driver, `protocol: 2`, `reconnect_attempts: 3`. To keep the eager behavior (immediate
|
|
75
|
+
`EMB` per call), opt out globally via `Emb.configure { |c| c.batch = false }` or per client
|
|
76
|
+
with `Emb.new(batch: false)`.
|
|
77
|
+
|
|
47
78
|
### Connection pool
|
|
48
79
|
|
|
49
80
|
```ruby
|
|
50
81
|
Emb.setup(url: "redis://localhost:6379", pool: 10)
|
|
51
82
|
```
|
|
52
83
|
|
|
84
|
+
The default pool size is **5**. The pool is usually not the bottleneck for
|
|
85
|
+
inference-bound workloads (small pools are fine); it becomes a knob only at high
|
|
86
|
+
concurrency on a multi-model box — see [Performance](#performance). If a pool checkout
|
|
87
|
+
would wait too long, `RedisClient`'s `connect_timeout`/`read_timeout` (above) bound the
|
|
88
|
+
wait.
|
|
89
|
+
|
|
53
90
|
### Authentication
|
|
54
91
|
|
|
55
92
|
If the server is configured with a password, include it in the URL:
|
|
@@ -89,8 +126,8 @@ Emb.setup(
|
|
|
89
126
|
```
|
|
90
127
|
|
|
91
128
|
See the [redis-client documentation](https://github.com/redis-rb/redis-client) for
|
|
92
|
-
all available options. Only `pool`
|
|
93
|
-
through to `RedisClient.new`.
|
|
129
|
+
all available options. Only `pool` and `batch` are handled by the gem — everything
|
|
130
|
+
else passes through to `RedisClient.new`.
|
|
94
131
|
|
|
95
132
|
## Instance-based clients
|
|
96
133
|
|
|
@@ -177,6 +214,144 @@ client.multi do |m|
|
|
|
177
214
|
end
|
|
178
215
|
```
|
|
179
216
|
|
|
217
|
+
### Lazy batching (`Emb.batch`)
|
|
218
|
+
|
|
219
|
+
Instead of collecting pairs by hand, `Emb.batch` returns lazy embeddings that all
|
|
220
|
+
coalesce into a single `EMB.MULTI` round trip when the first one is used. This is
|
|
221
|
+
powered by the [batch-loader](https://github.com/exAspArk/batch-loader) gem.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
users = User.all # some application objects
|
|
225
|
+
|
|
226
|
+
# Create loaders first...
|
|
227
|
+
l1 = Emb.batch[:minilm]["hello"]
|
|
228
|
+
l2 = Emb.batch[:minilm]["world"]
|
|
229
|
+
l3 = Emb.batch[:bge]["bonjour"]
|
|
230
|
+
|
|
231
|
+
# ...then consume them. The first use sends ONE EMB.MULTI for all three.
|
|
232
|
+
l1.sum # => 12.345
|
|
233
|
+
l2.sum # => -0.678
|
|
234
|
+
l3.sum # => 3.141
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Instance clients expose the same API:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
client.batch[:minilm]["hello"].sum
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Each lazy value materializes to the same shape as the eager API: a single text
|
|
244
|
+
yields an `Array<Float>`, multiple texts yield `Array<Array<Float>>`:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
vec = Emb.batch[:minilm]["hello"] # use -> Array of Float
|
|
248
|
+
vecs = Emb.batch[:minilm]["hello", "world"] # use -> Array of Array of Float
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Embeddings are cached per thread, so reusing a lazy value (or creating an
|
|
252
|
+
identical pair again in the same scope) is free after the first use. A pair whose
|
|
253
|
+
embedding fails materializes as `nil`, matching `EMB.MULTI`'s per-pair null
|
|
254
|
+
behavior; siblings in the same batch still succeed.
|
|
255
|
+
|
|
256
|
+
#### The create-then-consume contract
|
|
257
|
+
|
|
258
|
+
Loaders only fire when a value is **used**. Create all loaders *first*, then
|
|
259
|
+
consume them, so they share one round trip:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
texts.each { |t| process(Emb.batch[:minilm][t]) } # wrong: one MULTI per item
|
|
263
|
+
loaders = texts.map { |t| Emb.batch[:minilm][t] } # right: ONE MULTI for all
|
|
264
|
+
loaders.each { |l| process(l) }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
A loader that is created but never used **never embeds** (unless a sibling batch
|
|
268
|
+
fires first) and is silently dropped when the thread's scope ends. Duration and
|
|
269
|
+
scope: batching is per-thread — a multithreaded app issues one `EMB.MULTI` per
|
|
270
|
+
thread per flush.
|
|
271
|
+
|
|
272
|
+
### `batch` configuration option
|
|
273
|
+
|
|
274
|
+
Setting `batch: true` makes the standard proxy API lazy, so existing call sites
|
|
275
|
+
batch automatically without restructuring:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
Emb.setup(url: "redis://localhost:6379", batch: true)
|
|
279
|
+
# or
|
|
280
|
+
Emb.new(url: "redis://localhost:6379", batch: true)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
With `batch: true`, `Emb[:minilm]["hello"]` returns a lazy embedding that sends
|
|
284
|
+
`EMB.MULTI` on first use. The default is `false` — the proxy API stays eager,
|
|
285
|
+
sending `EMB` immediately. `Emb.batch` works regardless of the option, and
|
|
286
|
+
`Emb.multi` remains the explicit, eager, deterministic batching API.
|
|
287
|
+
|
|
288
|
+
### Clearing the cache per request
|
|
289
|
+
|
|
290
|
+
The per-thread batch scope holds cached embeddings for the life of the thread. In
|
|
291
|
+
request-shaped processes (Rails, Rack apps, Sidekiq) mount `Emb::Middleware` to
|
|
292
|
+
clear the scope at the end of each request:
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
# config/application.rb (Rails)
|
|
296
|
+
config.middleware.use Emb::Middleware
|
|
297
|
+
|
|
298
|
+
# Any Rack app
|
|
299
|
+
use Emb::Middleware
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The scope is cleared even when the app raises, and a fresh scope starts
|
|
303
|
+
automatically with the next request. Loaders created but never used within a
|
|
304
|
+
request are dropped — the create-then-consume contract applies per request.
|
|
305
|
+
|
|
306
|
+
## Performance
|
|
307
|
+
|
|
308
|
+
### Eager-burst pipelining (no new API)
|
|
309
|
+
|
|
310
|
+
For a burst of independent eager calls (feelers across call sites, fan-ins where
|
|
311
|
+
batching doesn't fit), coalesce them into one packet with `RedisClient#pipelined`:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
client = Emb.new(url: "redis://localhost:6379")
|
|
315
|
+
|
|
316
|
+
a = client.pool.with do |conn|
|
|
317
|
+
conn.pipelined do |pipe|
|
|
318
|
+
texts.each { |t| pipe.call("EMB", "minilm", t) }
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
# a -> Array of Float32-binary replies; unpack each with r.unpack("e*")
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`Client#pool` exposes the pool's `RedisClient` connections. This measurably beats
|
|
325
|
+
plain per-call round trips; use it for bursts, and `Emb.batch`/`Emb.multi` when you want
|
|
326
|
+
server-side coalescing into one `EMB.MULTI`.
|
|
327
|
+
|
|
328
|
+
### RESP driver: pure-Ruby default, `hiredis` on demand
|
|
329
|
+
|
|
330
|
+
The pure-Ruby RESP parser is the default. The C `hiredis` driver only meaningfully helps
|
|
331
|
+
the all-round-trip eager path (about +12% req/s) and is ~neutral for batched/pipelined
|
|
332
|
+
workloads, so it is not worth the native-build dependency by default. Enable it when
|
|
333
|
+
round-trip-heavy eager traffic dominates:
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
require "hiredis-client"
|
|
337
|
+
Emb.setup(url: "redis://localhost:6379", driver: :hiredis)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Horizontal scaling
|
|
341
|
+
|
|
342
|
+
emb instances are stateless — the model lives in memory and the LRU cache is per
|
|
343
|
+
instance. Scale out without a cluster client:
|
|
344
|
+
|
|
345
|
+
- **Model sharding** — run each instance with a subset of models; route by model via
|
|
346
|
+
dedicated clients (`Emb.new(url: "redis://model-a:6379")`).
|
|
347
|
+
- **Text-keyed sharding** — several instances serving the same model behind an L4 /
|
|
348
|
+
load balancer for same-model scale.
|
|
349
|
+
- **Cache warm-up** — because the cache is per instance, each new box starts cold
|
|
350
|
+
(optionally seed with `-cache` and a warmup pass).
|
|
351
|
+
|
|
352
|
+
Lazy batching stays per server (a thread's loaders target one client), so batching and
|
|
353
|
+
horizontal scale compose: each instance receives one `EMB.MULTI` per request.
|
|
354
|
+
|
|
180
355
|
### Commands
|
|
181
356
|
|
|
182
357
|
```ruby
|
data/lib/emb/batch.rb
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'batch_loader'
|
|
4
|
+
|
|
5
|
+
module Emb
|
|
6
|
+
BATCH_KEY = :emb
|
|
7
|
+
|
|
8
|
+
BATCH_BLOCK = lambda do |items, loader, _args|
|
|
9
|
+
items.group_by(&:first).each do |client, client_items|
|
|
10
|
+
pairs = client_items.flat_map { |_, model, text| Array(text).flat_map { |t| [model.to_s, t] } }
|
|
11
|
+
results = Array(client.send_command('EMB.MULTI', *pairs))
|
|
12
|
+
|
|
13
|
+
offset = 0
|
|
14
|
+
client_items.each do |item|
|
|
15
|
+
_, _, text = item
|
|
16
|
+
texts = Array(text)
|
|
17
|
+
values = results[offset, texts.size].map { |entry| entry&.unpack('e*') }
|
|
18
|
+
offset += texts.size
|
|
19
|
+
|
|
20
|
+
# eager Proxy#[] shape: vector for a single text, vectors for many
|
|
21
|
+
loader.call(item, values.size == 1 ? values.first : values)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
def build_batch_loader(client, model, text)
|
|
28
|
+
BatchLoader.for([client, model, text]).batch(key: BATCH_KEY, &BATCH_BLOCK)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class BatchProxy
|
|
33
|
+
def initialize(client)
|
|
34
|
+
@client = client
|
|
35
|
+
@models = {}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def [](name)
|
|
39
|
+
@models[name.to_sym] ||= BatchModelProxy.new(@client, name.to_sym)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def inspect
|
|
43
|
+
"#<Emb::BatchProxy client=#{@client.inspect}>"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class BatchModelProxy
|
|
48
|
+
attr_reader :name
|
|
49
|
+
|
|
50
|
+
def initialize(client, name)
|
|
51
|
+
@client = client
|
|
52
|
+
@name = name
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def [](text, *texts)
|
|
56
|
+
Emb.build_batch_loader(@client, @name, texts.empty? ? text : [text, *texts])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def inspect
|
|
60
|
+
"#<Emb::BatchModelProxy #{@name}>"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
data/lib/emb/client.rb
CHANGED
|
@@ -4,19 +4,17 @@ require 'connection_pool'
|
|
|
4
4
|
require 'redis_client'
|
|
5
5
|
|
|
6
6
|
module Emb
|
|
7
|
-
DEFAULTS = { host: 'localhost', port: 6379, pool: 5 }.freeze
|
|
8
|
-
|
|
9
7
|
class Client
|
|
10
8
|
attr_reader :pool
|
|
11
9
|
|
|
12
|
-
def initialize(pool:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
redis_options
|
|
17
|
-
redis_options
|
|
10
|
+
def initialize(pool: nil, batch: nil, **redis_options)
|
|
11
|
+
cfg = Emb.configuration
|
|
12
|
+
@batch_enabled = batch.nil? ? cfg.batch : batch
|
|
13
|
+
size = pool.nil? ? cfg.pool : pool
|
|
14
|
+
url = extract_url!(redis_options, cfg)
|
|
15
|
+
redis_options = merged_redis_options(redis_options, cfg, url)
|
|
18
16
|
|
|
19
|
-
@pool = ConnectionPool.new(size:
|
|
17
|
+
@pool = ConnectionPool.new(size: size) do
|
|
20
18
|
RedisClient.new(url: url, **redis_options)
|
|
21
19
|
end
|
|
22
20
|
|
|
@@ -39,6 +37,14 @@ module Emb
|
|
|
39
37
|
@registry[name] ||= Proxy.new(self, name.to_sym)
|
|
40
38
|
end
|
|
41
39
|
|
|
40
|
+
def batch
|
|
41
|
+
@batch ||= BatchProxy.new(self)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def batch?
|
|
45
|
+
@batch_enabled
|
|
46
|
+
end
|
|
47
|
+
|
|
42
48
|
def models
|
|
43
49
|
raw = send_command('EMB.MODELS')
|
|
44
50
|
return [] if raw.nil?
|
|
@@ -66,7 +72,7 @@ module Emb
|
|
|
66
72
|
def ready
|
|
67
73
|
send_command('EMB.READY')
|
|
68
74
|
|
|
69
|
-
|
|
75
|
+
'ready'
|
|
70
76
|
rescue RedisClient::CommandError => e
|
|
71
77
|
e.message
|
|
72
78
|
end
|
|
@@ -91,9 +97,23 @@ module Emb
|
|
|
91
97
|
|
|
92
98
|
private
|
|
93
99
|
|
|
94
|
-
def
|
|
100
|
+
def merged_redis_options(opts, cfg, url)
|
|
101
|
+
defaults = cfg.to_h
|
|
102
|
+
keys = defaults.keys - %i[url pool batch]
|
|
103
|
+
keys -= %i[host port] if url
|
|
104
|
+
|
|
105
|
+
keys.each do |key|
|
|
106
|
+
opts[key] = defaults[key] if opts[key].nil? && !defaults[key].nil?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
opts
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def extract_url!(opts, cfg)
|
|
95
113
|
url = opts.delete(:url)
|
|
96
|
-
url
|
|
114
|
+
return url unless url.nil?
|
|
115
|
+
|
|
116
|
+
cfg.url || ENV.fetch('EMB_URL', nil)
|
|
97
117
|
end
|
|
98
118
|
end
|
|
99
119
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Emb
|
|
4
|
+
class Configuration
|
|
5
|
+
OPTIONS = %i[
|
|
6
|
+
host port url pool batch driver protocol
|
|
7
|
+
connect_timeout read_timeout write_timeout reconnect_attempts
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
attr_accessor(*OPTIONS)
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
self.host = 'localhost'
|
|
14
|
+
self.port = 6379
|
|
15
|
+
self.url = nil
|
|
16
|
+
self.pool = 5
|
|
17
|
+
self.batch = true
|
|
18
|
+
self.driver = nil
|
|
19
|
+
self.protocol = 2
|
|
20
|
+
self.connect_timeout = nil
|
|
21
|
+
self.read_timeout = nil
|
|
22
|
+
self.write_timeout = nil
|
|
23
|
+
self.reconnect_attempts = 3
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
OPTIONS.to_h { |key| [key, public_send(key)] }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'batch_loader'
|
|
4
|
+
|
|
5
|
+
module Emb
|
|
6
|
+
# Rack middleware that clears the per-thread batch scope at the end of each
|
|
7
|
+
# request, bounding cache growth in long-lived request threads. The scope is
|
|
8
|
+
# cleared even when the app raises; a fresh scope starts with the next request.
|
|
9
|
+
class Middleware
|
|
10
|
+
def initialize(app)
|
|
11
|
+
@app = app
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(env)
|
|
15
|
+
@app.call(env)
|
|
16
|
+
ensure
|
|
17
|
+
BatchLoader::Executor.clear_current
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/emb/proxy.rb
CHANGED
|
@@ -10,6 +10,8 @@ module Emb
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def [](text, *texts)
|
|
13
|
+
return Emb.build_batch_loader(@client, @name, texts.empty? ? text : [text, *texts]) if @client.batch?
|
|
14
|
+
|
|
13
15
|
set = Array(@client.send_command('EMB', @name.to_s, text, *texts))
|
|
14
16
|
result = set.map { |entry| entry.unpack('e*') }
|
|
15
17
|
|
data/lib/emb.rb
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'emb/version'
|
|
4
|
+
require_relative 'emb/configuration'
|
|
4
5
|
require_relative 'emb/client'
|
|
5
6
|
require_relative 'emb/proxy'
|
|
6
7
|
require_relative 'emb/multi'
|
|
8
|
+
require_relative 'emb/batch'
|
|
9
|
+
require_relative 'emb/middleware'
|
|
7
10
|
|
|
8
11
|
module Emb
|
|
9
12
|
class << self
|
|
@@ -15,7 +18,17 @@ module Emb
|
|
|
15
18
|
|
|
16
19
|
alias config setup
|
|
17
20
|
|
|
21
|
+
def configuration
|
|
22
|
+
@configuration ||= Configuration.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def configure
|
|
26
|
+
yield configuration if block_given?
|
|
27
|
+
configuration
|
|
28
|
+
end
|
|
29
|
+
|
|
18
30
|
def [](name) = default_client[name]
|
|
31
|
+
def batch = default_client.batch
|
|
19
32
|
def models = default_client.models
|
|
20
33
|
def info(name) = default_client.info(name)
|
|
21
34
|
def stats = default_client.stats
|
metadata
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: emb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- elcuervo
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: batch-loader
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: connection_pool
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -48,7 +62,10 @@ files:
|
|
|
48
62
|
- Gemfile
|
|
49
63
|
- README.md
|
|
50
64
|
- lib/emb.rb
|
|
65
|
+
- lib/emb/batch.rb
|
|
51
66
|
- lib/emb/client.rb
|
|
67
|
+
- lib/emb/configuration.rb
|
|
68
|
+
- lib/emb/middleware.rb
|
|
52
69
|
- lib/emb/multi.rb
|
|
53
70
|
- lib/emb/proxy.rb
|
|
54
71
|
- lib/emb/version.rb
|