feat: index active proxies by worker
This commit is contained in:
parent
27035947dd
commit
2c097f3e89
@ -45,6 +45,7 @@ type ownershipRecord struct {
|
|||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
ProxyID string `json:"proxyId"`
|
ProxyID string `json:"proxyId"`
|
||||||
WorkerID string `json:"workerId"`
|
WorkerID string `json:"workerId"`
|
||||||
|
WorkerIndexKey string `json:"workerIndexKey,omitempty"`
|
||||||
Epoch uint64 `json:"epoch"`
|
Epoch uint64 `json:"epoch"`
|
||||||
AssignmentVersion uint64 `json:"assignmentVersion"`
|
AssignmentVersion uint64 `json:"assignmentVersion"`
|
||||||
ExpiresAtMS int64 `json:"expiresAtMs"`
|
ExpiresAtMS int64 `json:"expiresAtMs"`
|
||||||
@ -221,6 +222,9 @@ func validateOwnershipRecord(record ownershipRecord) error {
|
|||||||
record.Epoch == 0 || record.AssignmentVersion == 0 || record.ExpiresAtMS <= 0 {
|
record.Epoch == 0 || record.AssignmentVersion == 0 || record.ExpiresAtMS <= 0 {
|
||||||
return ErrInvalidRecord
|
return ErrInvalidRecord
|
||||||
}
|
}
|
||||||
|
if record.WorkerIndexKey != "" && !strings.Contains(record.WorkerIndexKey, "{activity}") {
|
||||||
|
return ErrInvalidRecord
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -83,6 +83,10 @@ func (keys keyspace) owned(value string) string {
|
|||||||
return keys.facet("owned", value)
|
return keys.facet("owned", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (keys keyspace) workerOwned(workerID string) string {
|
||||||
|
return keys.facet("worker-owned", workerID)
|
||||||
|
}
|
||||||
|
|
||||||
func (keys keyspace) facet(name, value string) string {
|
func (keys keyspace) facet(name, value string) string {
|
||||||
return keys.prefix + ":" + name + ":" + digestToken(value)
|
return keys.prefix + ":" + name + ":" + digestToken(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -226,7 +226,7 @@ func (a *Adapter) runOwnership(
|
|||||||
a.keys.inventory, a.keys.stateInventory, a.keys.owners, a.keys.ownerExpiry,
|
a.keys.inventory, a.keys.stateInventory, a.keys.owners, a.keys.ownerExpiry,
|
||||||
a.keys.epoch, operationKey,
|
a.keys.epoch, operationKey,
|
||||||
}, operation, operationTTLMillis(a.options.OperationTTL), a.options.CleanupLimit,
|
}, operation, operationTTLMillis(a.options.OperationTTL), a.options.CleanupLimit,
|
||||||
nowMS, proxyID, workerID, epoch, value, active, reserved)
|
nowMS, proxyID, workerID, epoch, value, active, reserved, a.keys.workerOwned(workerID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ownershipScriptReply{}, err
|
return ownershipScriptReply{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ func TestRedisOwnershipLifecycle(t *testing.T) {
|
|||||||
if current, ok, err := fixture.Adapter.Get(context.Background(), "proxy-a"); err != nil || !ok || current != assigned {
|
if current, ok, err := fixture.Adapter.Get(context.Background(), "proxy-a"); err != nil || !ok || current != assigned {
|
||||||
t.Fatalf("Get(assigned) = %+v, %t, %v", current, ok, err)
|
t.Fatalf("Get(assigned) = %+v, %t, %v", current, ok, err)
|
||||||
}
|
}
|
||||||
|
assertRedisWorkerOwned(t, fixture, "worker-a", "proxy-a")
|
||||||
blocked, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
blocked, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
||||||
RequestID: "req-owned", ClientID: "client-a", Requested: 1,
|
RequestID: "req-owned", ClientID: "client-a", Requested: 1,
|
||||||
Fulfillment: extractionDomain.Partial, Now: now.Add(3 * time.Second),
|
Fulfillment: extractionDomain.Partial, Now: now.Add(3 * time.Second),
|
||||||
@ -50,6 +51,9 @@ func TestRedisOwnershipLifecycle(t *testing.T) {
|
|||||||
!renewed.ExpiresAt.Equal(now.Add(2*time.Minute)) {
|
!renewed.ExpiresAt.Equal(now.Add(2*time.Minute)) {
|
||||||
t.Fatalf("Renew() = %+v, %v", renewed, err)
|
t.Fatalf("Renew() = %+v, %v", renewed, err)
|
||||||
}
|
}
|
||||||
|
if score, err := fixture.Client.ZScore(context.Background(), fixture.Adapter.keys.workerOwned("worker-a"), "proxy-a").Result(); err != nil || int64(score) != renewed.ExpiresAt.UnixMilli() {
|
||||||
|
t.Fatalf("worker-owned score = %f, %v; want %d", score, err, renewed.ExpiresAt.UnixMilli())
|
||||||
|
}
|
||||||
if _, err := fixture.Adapter.Renew(context.Background(), now.Add(31*time.Second),
|
if _, err := fixture.Adapter.Renew(context.Background(), now.Add(31*time.Second),
|
||||||
"proxy-a", "worker-a", assigned.Epoch+1, time.Minute); !errors.Is(err, ownershipDomain.ErrStaleAssignment) {
|
"proxy-a", "worker-a", assigned.Epoch+1, time.Minute); !errors.Is(err, ownershipDomain.ErrStaleAssignment) {
|
||||||
t.Fatalf("Renew(stale epoch) error = %v", err)
|
t.Fatalf("Renew(stale epoch) error = %v", err)
|
||||||
@ -59,6 +63,7 @@ func TestRedisOwnershipLifecycle(t *testing.T) {
|
|||||||
if err != nil || !draining.Draining || draining.Version != renewed.Version+1 {
|
if err != nil || !draining.Draining || draining.Version != renewed.Version+1 {
|
||||||
t.Fatalf("BeginDrain() = %+v, %v", draining, err)
|
t.Fatalf("BeginDrain() = %+v, %v", draining, err)
|
||||||
}
|
}
|
||||||
|
assertRedisWorkerOwned(t, fixture, "worker-a")
|
||||||
replayed, err := fixture.Adapter.BeginDrain(context.Background(), "proxy-a", "worker-a", assigned.Epoch)
|
replayed, err := fixture.Adapter.BeginDrain(context.Background(), "proxy-a", "worker-a", assigned.Epoch)
|
||||||
if err != nil || replayed != draining {
|
if err != nil || replayed != draining {
|
||||||
t.Fatalf("BeginDrain(replay) = %+v, %v", replayed, err)
|
t.Fatalf("BeginDrain(replay) = %+v, %v", replayed, err)
|
||||||
@ -72,6 +77,7 @@ func TestRedisOwnershipLifecycle(t *testing.T) {
|
|||||||
if err := fixture.Adapter.AcknowledgeDrain(context.Background(), "proxy-a", "worker-a", assigned.Epoch, 0, 0); err != nil {
|
if err := fixture.Adapter.AcknowledgeDrain(context.Background(), "proxy-a", "worker-a", assigned.Epoch, 0, 0); err != nil {
|
||||||
t.Fatalf("AcknowledgeDrain(): %v", err)
|
t.Fatalf("AcknowledgeDrain(): %v", err)
|
||||||
}
|
}
|
||||||
|
assertRedisWorkerOwned(t, fixture, "worker-a")
|
||||||
assertRedisKeysHaveTTL(t, fixture,
|
assertRedisKeysHaveTTL(t, fixture,
|
||||||
fixture.Adapter.keys.available,
|
fixture.Adapter.keys.available,
|
||||||
fixture.Adapter.keys.protocol("http"),
|
fixture.Adapter.keys.protocol("http"),
|
||||||
@ -355,6 +361,7 @@ func TestRedisSweepExpiredIsLimitedAndCleansOwnership(t *testing.T) {
|
|||||||
ownedFixture.Adapter.keys.region("cn"),
|
ownedFixture.Adapter.keys.region("cn"),
|
||||||
ownedFixture.Adapter.keys.carrier("ct"),
|
ownedFixture.Adapter.keys.carrier("ct"),
|
||||||
ownedFixture.Adapter.keys.upstream("provider-a"),
|
ownedFixture.Adapter.keys.upstream("provider-a"),
|
||||||
|
ownedFixture.Adapter.keys.workerOwned("worker-a"),
|
||||||
} {
|
} {
|
||||||
if count, err := ownedFixture.Client.ZCard(context.Background(), sortedSet).Result(); err != nil || count != 0 {
|
if count, err := ownedFixture.Client.ZCard(context.Background(), sortedSet).Result(); err != nil || count != 0 {
|
||||||
t.Fatalf("sorted set %s entries after sweep = %d, %v", sortedSet, count, err)
|
t.Fatalf("sorted set %s entries after sweep = %d, %v", sortedSet, count, err)
|
||||||
@ -399,6 +406,22 @@ func assertRedisInventory(t *testing.T, adapter *Adapter, upstreamID string, now
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertRedisWorkerOwned(t *testing.T, fixture redisTestFixture, workerID string, want ...string) {
|
||||||
|
t.Helper()
|
||||||
|
actual, err := fixture.Client.ZRange(context.Background(), fixture.Adapter.keys.workerOwned(workerID), 0, -1).Result()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ZRange(worker-owned %s): %v", workerID, err)
|
||||||
|
}
|
||||||
|
if len(actual) != len(want) {
|
||||||
|
t.Fatalf("worker-owned %s = %v, want %v", workerID, actual, want)
|
||||||
|
}
|
||||||
|
for index, proxyID := range want {
|
||||||
|
if actual[index] != proxyID {
|
||||||
|
t.Fatalf("worker-owned %s = %v, want %v", workerID, actual, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func assertRedisKeysHaveTTL(t *testing.T, fixture redisTestFixture, keys ...string) {
|
func assertRedisKeysHaveTTL(t *testing.T, fixture redisTestFixture, keys ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
|
|||||||
@ -119,6 +119,18 @@ local function remove_owned(proxy_id, record)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function remove_worker_owned(proxy_id)
|
||||||
|
local raw = redis.call('HGET', owners_key, proxy_id)
|
||||||
|
if not raw then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local decoded, assignment = pcall(cjson.decode, raw)
|
||||||
|
if decoded and type(assignment) == 'table' and
|
||||||
|
type(assignment.workerIndexKey) == 'string' and assignment.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZREM', assignment.workerIndexKey, proxy_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function remove_proxy(proxy_id)
|
local function remove_proxy(proxy_id)
|
||||||
local raw = redis.call('HGET', records_key, proxy_id)
|
local raw = redis.call('HGET', records_key, proxy_id)
|
||||||
local record = nil
|
local record = nil
|
||||||
@ -143,6 +155,7 @@ local function remove_proxy(proxy_id)
|
|||||||
redis.call('HDEL', idkeys_key, proxy_id)
|
redis.call('HDEL', idkeys_key, proxy_id)
|
||||||
redis.call('HDEL', records_key, proxy_id)
|
redis.call('HDEL', records_key, proxy_id)
|
||||||
redis.call('ZREM', expiry_key, proxy_id)
|
redis.call('ZREM', expiry_key, proxy_id)
|
||||||
|
remove_worker_owned(proxy_id)
|
||||||
redis.call('HDEL', owners_key, proxy_id)
|
redis.call('HDEL', owners_key, proxy_id)
|
||||||
redis.call('ZREM', owner_expiry_key, proxy_id)
|
redis.call('ZREM', owner_expiry_key, proxy_id)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -76,6 +76,18 @@ local function remove_owned(id, record)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function remove_worker_owned(id)
|
||||||
|
local raw = redis.call('HGET', owners_key, id)
|
||||||
|
if not raw then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local decoded, assignment = pcall(cjson.decode, raw)
|
||||||
|
if decoded and type(assignment) == 'table' and
|
||||||
|
type(assignment.workerIndexKey) == 'string' and assignment.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZREM', assignment.workerIndexKey, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function remove_proxy(id)
|
local function remove_proxy(id)
|
||||||
local raw = redis.call('HGET', records_key, id)
|
local raw = redis.call('HGET', records_key, id)
|
||||||
local record = nil
|
local record = nil
|
||||||
@ -97,6 +109,7 @@ local function remove_proxy(id)
|
|||||||
redis.call('HDEL', idkeys_key, id)
|
redis.call('HDEL', idkeys_key, id)
|
||||||
redis.call('HDEL', records_key, id)
|
redis.call('HDEL', records_key, id)
|
||||||
redis.call('ZREM', expiry_key, id)
|
redis.call('ZREM', expiry_key, id)
|
||||||
|
remove_worker_owned(id)
|
||||||
redis.call('HDEL', owners_key, id)
|
redis.call('HDEL', owners_key, id)
|
||||||
redis.call('ZREM', owner_expiry_key, id)
|
redis.call('ZREM', owner_expiry_key, id)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -20,6 +20,7 @@ local epoch = tonumber(ARGV[7])
|
|||||||
local value = tonumber(ARGV[8])
|
local value = tonumber(ARGV[8])
|
||||||
local active = tonumber(ARGV[9])
|
local active = tonumber(ARGV[9])
|
||||||
local reserved = tonumber(ARGV[10])
|
local reserved = tonumber(ARGV[10])
|
||||||
|
local worker_index_key = ARGV[11]
|
||||||
local mutating = operation ~= 'get'
|
local mutating = operation ~= 'get'
|
||||||
|
|
||||||
local function finish(reply)
|
local function finish(reply)
|
||||||
@ -185,6 +186,9 @@ local function clear_owner(id, assignment, at_ms, restore)
|
|||||||
end
|
end
|
||||||
redis.call('HDEL', owners_key, id)
|
redis.call('HDEL', owners_key, id)
|
||||||
redis.call('ZREM', owner_expiry_key, id)
|
redis.call('ZREM', owner_expiry_key, id)
|
||||||
|
if assignment and type(assignment.workerIndexKey) == 'string' and assignment.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZREM', assignment.workerIndexKey, id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if operation == 'assign' then
|
if operation == 'assign' then
|
||||||
@ -209,6 +213,10 @@ if operation == 'assign' then
|
|||||||
not tonumber(record.usableUntilMs) or tonumber(record.usableUntilMs) <= now_ms then
|
not tonumber(record.usableUntilMs) or tonumber(record.usableUntilMs) <= now_ms then
|
||||||
return finish({status = 'unavailable'})
|
return finish({status = 'unavailable'})
|
||||||
end
|
end
|
||||||
|
if type(worker_index_key) ~= 'string' or worker_index_key == '' or
|
||||||
|
not string.find(worker_index_key, '{activity}', 1, true) then
|
||||||
|
return finish({status = 'invalid'})
|
||||||
|
end
|
||||||
local expires_at_ms = now_ms + value
|
local expires_at_ms = now_ms + value
|
||||||
if tonumber(record.usableUntilMs) < expires_at_ms then
|
if tonumber(record.usableUntilMs) < expires_at_ms then
|
||||||
expires_at_ms = tonumber(record.usableUntilMs)
|
expires_at_ms = tonumber(record.usableUntilMs)
|
||||||
@ -219,6 +227,7 @@ if operation == 'assign' then
|
|||||||
version = 1,
|
version = 1,
|
||||||
proxyId = proxy_id,
|
proxyId = proxy_id,
|
||||||
workerId = worker_id,
|
workerId = worker_id,
|
||||||
|
workerIndexKey = worker_index_key,
|
||||||
epoch = next_epoch,
|
epoch = next_epoch,
|
||||||
assignmentVersion = 1,
|
assignmentVersion = 1,
|
||||||
expiresAtMs = expires_at_ms,
|
expiresAtMs = expires_at_ms,
|
||||||
@ -234,6 +243,8 @@ if operation == 'assign' then
|
|||||||
remove_available(proxy_id, record)
|
remove_available(proxy_id, record)
|
||||||
redis.call('ZADD', record.ownerIndexKey, record.usableUntilMs, proxy_id)
|
redis.call('ZADD', record.ownerIndexKey, record.usableUntilMs, proxy_id)
|
||||||
touch(record.ownerIndexKey, tonumber(record.expiresAtMs))
|
touch(record.ownerIndexKey, tonumber(record.expiresAtMs))
|
||||||
|
redis.call('ZADD', worker_index_key, expires_at_ms, proxy_id)
|
||||||
|
touch(worker_index_key, tonumber(record.expiresAtMs))
|
||||||
return finish({status = 'ok', record = encoded})
|
return finish({status = 'ok', record = encoded})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -264,6 +275,10 @@ if operation == 'renew' then
|
|||||||
redis.call('ZADD', owner_expiry_key, expires_at_ms, proxy_id)
|
redis.call('ZADD', owner_expiry_key, expires_at_ms, proxy_id)
|
||||||
touch(owners_key, tonumber(record.expiresAtMs))
|
touch(owners_key, tonumber(record.expiresAtMs))
|
||||||
touch(owner_expiry_key, tonumber(record.expiresAtMs))
|
touch(owner_expiry_key, tonumber(record.expiresAtMs))
|
||||||
|
if not current.draining and type(current.workerIndexKey) == 'string' and current.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZADD', current.workerIndexKey, expires_at_ms, proxy_id)
|
||||||
|
touch(current.workerIndexKey, tonumber(record.expiresAtMs))
|
||||||
|
end
|
||||||
return finish({status = 'ok', record = encoded})
|
return finish({status = 'ok', record = encoded})
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -279,6 +294,9 @@ if operation == 'begin_drain' then
|
|||||||
redis.call('HSET', owners_key, proxy_id, encoded)
|
redis.call('HSET', owners_key, proxy_id, encoded)
|
||||||
local record = decode_table(redis.call('HGET', records_key, proxy_id))
|
local record = decode_table(redis.call('HGET', records_key, proxy_id))
|
||||||
remove_owned(proxy_id, record)
|
remove_owned(proxy_id, record)
|
||||||
|
if type(current.workerIndexKey) == 'string' and current.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZREM', current.workerIndexKey, proxy_id)
|
||||||
|
end
|
||||||
return finish({status = 'ok', record = encoded})
|
return finish({status = 'ok', record = encoded})
|
||||||
end
|
end
|
||||||
return finish({status = 'ok', record = cjson.encode(current)})
|
return finish({status = 'ok', record = cjson.encode(current)})
|
||||||
|
|||||||
@ -79,6 +79,18 @@ local function remove_owned(proxy_id, record)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function remove_worker_owned(proxy_id)
|
||||||
|
local raw = redis.call('HGET', owners_key, proxy_id)
|
||||||
|
if not raw then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local decoded, assignment = pcall(cjson.decode, raw)
|
||||||
|
if decoded and type(assignment) == 'table' and
|
||||||
|
type(assignment.workerIndexKey) == 'string' and assignment.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZREM', assignment.workerIndexKey, proxy_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function remove_proxy(proxy_id)
|
local function remove_proxy(proxy_id)
|
||||||
local raw = redis.call('HGET', records_key, proxy_id)
|
local raw = redis.call('HGET', records_key, proxy_id)
|
||||||
local record = nil
|
local record = nil
|
||||||
@ -103,6 +115,7 @@ local function remove_proxy(proxy_id)
|
|||||||
redis.call('HDEL', idkeys_key, proxy_id)
|
redis.call('HDEL', idkeys_key, proxy_id)
|
||||||
redis.call('HDEL', records_key, proxy_id)
|
redis.call('HDEL', records_key, proxy_id)
|
||||||
redis.call('ZREM', expiry_key, proxy_id)
|
redis.call('ZREM', expiry_key, proxy_id)
|
||||||
|
remove_worker_owned(proxy_id)
|
||||||
redis.call('HDEL', owners_key, proxy_id)
|
redis.call('HDEL', owners_key, proxy_id)
|
||||||
redis.call('ZREM', owner_expiry_key, proxy_id)
|
redis.call('ZREM', owner_expiry_key, proxy_id)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -76,6 +76,18 @@ local function remove_owned(proxy_id, record)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function remove_worker_owned(proxy_id)
|
||||||
|
local raw = redis.call('HGET', owners_key, proxy_id)
|
||||||
|
if not raw then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local decoded, assignment = pcall(cjson.decode, raw)
|
||||||
|
if decoded and type(assignment) == 'table' and
|
||||||
|
type(assignment.workerIndexKey) == 'string' and assignment.workerIndexKey ~= '' then
|
||||||
|
redis.call('ZREM', assignment.workerIndexKey, proxy_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local function remove_proxy(proxy_id)
|
local function remove_proxy(proxy_id)
|
||||||
local raw = redis.call('HGET', records_key, proxy_id)
|
local raw = redis.call('HGET', records_key, proxy_id)
|
||||||
local record = nil
|
local record = nil
|
||||||
@ -97,6 +109,7 @@ local function remove_proxy(proxy_id)
|
|||||||
redis.call('HDEL', idkeys_key, proxy_id)
|
redis.call('HDEL', idkeys_key, proxy_id)
|
||||||
redis.call('HDEL', records_key, proxy_id)
|
redis.call('HDEL', records_key, proxy_id)
|
||||||
redis.call('ZREM', expiry_key, proxy_id)
|
redis.call('ZREM', expiry_key, proxy_id)
|
||||||
|
remove_worker_owned(proxy_id)
|
||||||
redis.call('HDEL', owners_key, proxy_id)
|
redis.call('HDEL', owners_key, proxy_id)
|
||||||
redis.call('ZREM', owner_expiry_key, proxy_id)
|
redis.call('ZREM', owner_expiry_key, proxy_id)
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user