Skip to content

Commit 1d74dfd

Browse files
kpulipati29Krishna mohan Pulipati
andauthored
fix: Refactor/minor cleanup (#334)
* fix: Make max commands per pipe configurable * fix: Add same changes to redis * fix: type issue * fix: typing * fix: typing * fix: typing --------- Co-authored-by: Krishna mohan Pulipati <kpulipati@expediagroup.com>
1 parent abcc6dc commit 1d74dfd

3 files changed

Lines changed: 50 additions & 12 deletions

File tree

sdk/python/feast/infra/online_stores/eg_valkey.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import json
1515
import logging
1616
import math
17+
import os
1718
import time
1819
from datetime import datetime, timedelta, timezone
1920
from enum import Enum
@@ -96,6 +97,9 @@ class EGValkeyOnlineStoreConfig(FeastConfigBaseModel):
9697
read_batch_size: Optional[int] = 100
9798
"""(Optional) number of keys to read in a single batch for online read requests. Anything < 1 means no batching."""
9899

100+
max_pipeline_commands: Optional[int] = 500
101+
"""(Optional) The maximum number of Valkey commands to queue in a pipeline before sending them to Valkey in a single batch."""
102+
99103

100104
class EGValkeyOnlineStore(OnlineStore):
101105
"""
@@ -327,10 +331,14 @@ def online_write_batch(
327331
)
328332

329333
sort_key_name = table.sort_keys[0].name
334+
330335
num_cmds = 0
331-
# Picking an arbitrary number to start with and will be tuned after perf testing
332-
# TODO : Make this a config as this can be different for different users based on payload size etc..
333-
num_cmds_per_pipeline_execute = 500
336+
max_pipeline_commands_per_process = (
337+
EGValkeyOnlineStore._get_max_pipeline_commands_per_process(
338+
online_store_config.max_pipeline_commands
339+
)
340+
)
341+
334342
ttl_feature_view = table.ttl
335343

336344
for entity_key, values, timestamp, _ in data:
@@ -381,7 +389,7 @@ def online_write_batch(
381389
pipe.expire(name=hash_key, time=ttl)
382390
num_cmds += 1
383391

384-
if num_cmds >= num_cmds_per_pipeline_execute:
392+
if num_cmds >= max_pipeline_commands_per_process:
385393
# TODO: May be add retries with backoff
386394
try:
387395
results = pipe.execute() # flush
@@ -414,14 +422,13 @@ def online_write_batch(
414422
# AFTER batch flush: run TTL cleanup
415423
if run_cleanup_by_event_time and ttl_feature_view_seconds:
416424
cleanup_cmds = 0
417-
cleanup_cmds_per_execute = 500
418425
cutoff = (int(time.time()) - ttl_feature_view_seconds) * 1000
419426
for zset_key, entity_key_bytes in zsets_to_cleanup:
420427
self._run_cleanup_by_event_time(
421428
pipe, zset_key, ttl_feature_view_seconds, cutoff
422429
)
423430
cleanup_cmds += 2
424-
if cleanup_cmds >= cleanup_cmds_per_execute:
431+
if cleanup_cmds >= max_pipeline_commands_per_process:
425432
try:
426433
pipe.execute()
427434
except ValkeyError:
@@ -539,6 +546,17 @@ def _get_ttl(
539546
ttl_remaining = timestamp - utils._utc_now() + ttl_offset
540547
return math.ceil(ttl_remaining.total_seconds())
541548

549+
@staticmethod
550+
def _get_max_pipeline_commands_per_process(
551+
max_pipeline_commands: int | None,
552+
) -> int:
553+
assert max_pipeline_commands is not None
554+
num_processes = int(os.environ.get("NUM_PROCESSES", 1))
555+
max_pipeline_commands_per_process = max(
556+
1, math.ceil(max_pipeline_commands / num_processes)
557+
)
558+
return max_pipeline_commands_per_process
559+
542560
def _run_cleanup_by_event_time(
543561
self, pipe, zset_key: bytes, ttl_seconds: int, cutoff
544562
):

sdk/python/feast/infra/online_stores/redis.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import json
1515
import logging
1616
import math
17+
import os
1718
import time
1819
from datetime import datetime, timedelta, timezone
1920
from enum import Enum
@@ -88,6 +89,9 @@ class RedisOnlineStoreConfig(FeastConfigBaseModel):
8889
read_batch_size: Optional[int] = 100
8990
"""(Optional) number of keys to read in a single batch for online read requests. Anything < 1 means no batching."""
9091

92+
max_pipeline_commands: Optional[int] = 500
93+
"""(Optional) The maximum number of Redis commands to queue in a pipeline before sending them to Redis in a single batch.In most cases, limiting the size of the pipeline to 100-1000 operations per shard gives the best results."""
94+
9195

9296
class RedisOnlineStore(OnlineStore):
9397
"""
@@ -321,9 +325,12 @@ def online_write_batch(
321325

322326
sort_key_name = table.sort_keys[0].name
323327
num_cmds = 0
324-
# Picking an arbitrary number to start with and will be tuned after perf testing
325-
# TODO : Make this a config as this can be different for different users based on payload size etc..
326-
num_cmds_per_pipeline_execute = 500
328+
max_pipeline_commands_per_process = (
329+
RedisOnlineStore._get_max_pipeline_commands_per_process(
330+
online_store_config.max_pipeline_commands
331+
)
332+
)
333+
327334
ttl_feature_view = table.ttl
328335

329336
for entity_key, values, timestamp, _ in data:
@@ -374,7 +381,7 @@ def online_write_batch(
374381
pipe.expire(name=hash_key, time=ttl)
375382
num_cmds += 1
376383

377-
if num_cmds >= num_cmds_per_pipeline_execute:
384+
if num_cmds >= max_pipeline_commands_per_process:
378385
# TODO: May be add retries with backoff
379386
try:
380387
results = pipe.execute() # flush
@@ -407,14 +414,13 @@ def online_write_batch(
407414
# AFTER batch flush: run TTL cleanup
408415
if run_cleanup_by_event_time and ttl_feature_view_seconds:
409416
cleanup_cmds = 0
410-
cleanup_cmds_per_execute = 500
411417
cutoff = (int(time.time()) - ttl_feature_view_seconds) * 1000
412418
for zset_key, entity_key_bytes in zsets_to_cleanup:
413419
self._run_cleanup_by_event_time(
414420
pipe, zset_key, ttl_feature_view_seconds, cutoff
415421
)
416422
cleanup_cmds += 2
417-
if cleanup_cmds >= cleanup_cmds_per_execute:
423+
if cleanup_cmds >= max_pipeline_commands_per_process:
418424
try:
419425
pipe.execute()
420426
except RedisError:
@@ -518,6 +524,17 @@ def sort_key_bytes(sort_key_name: str, sort_val: ValueProto, v: int = 3) -> byte
518524
sk = EntityKeyProto(join_keys=[sort_key_name], entity_values=[sort_val])
519525
return serialize_entity_key(sk, entity_key_serialization_version=v)
520526

527+
@staticmethod
528+
def _get_max_pipeline_commands_per_process(
529+
max_pipeline_commands: int | None,
530+
) -> int:
531+
assert max_pipeline_commands is not None
532+
num_processes = int(os.environ.get("NUM_PROCESSES", 1))
533+
max_pipeline_commands_per_process = max(
534+
1, math.ceil(max_pipeline_commands / num_processes)
535+
)
536+
return max_pipeline_commands_per_process
537+
521538
@staticmethod
522539
def _get_ttl(
523540
ttl_feature_view: timedelta,

sdk/python/feast/infra/passthrough_provider.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,8 @@ def ingest_df(
403403
if table.num_rows < num_processes:
404404
num_processes = table.num_rows
405405

406+
os.environ["NUM_PROCESSES"] = str(num_processes)
407+
406408
# Input table is split into smaller chunks and processed in parallel
407409
chunks = self.split_table(num_processes, table)
408410

@@ -413,6 +415,7 @@ def ingest_df(
413415
with Pool(processes=num_processes) as pool:
414416
pool.starmap(self.process, chunks_to_parallelize)
415417
else:
418+
os.environ["NUM_PROCESSES"] = "1"
416419
self.process(table, feature_view, join_keys)
417420

418421
@staticmethod

0 commit comments

Comments
 (0)