1616 */
1717package feast .storage .connectors .redis .retriever ;
1818
19- import com .google .protobuf .AbstractMessageLite ;
2019import com .google .protobuf .InvalidProtocolBufferException ;
2120import feast .proto .core .FeatureSetProto .EntitySpec ;
2221import feast .proto .core .FeatureSetProto .FeatureSetSpec ;
2726import feast .proto .types .ValueProto .Value ;
2827import feast .storage .api .retriever .FeatureSetRequest ;
2928import feast .storage .api .retriever .OnlineRetriever ;
29+ import feast .storage .connectors .redis .serializer .RedisKeyPrefixSerializer ;
30+ import feast .storage .connectors .redis .serializer .RedisKeySerializer ;
3031import io .grpc .Status ;
3132import io .lettuce .core .RedisURI ;
3233import io .lettuce .core .cluster .RedisClusterClient ;
3334import io .lettuce .core .cluster .api .StatefulRedisClusterConnection ;
3435import io .lettuce .core .cluster .api .sync .RedisAdvancedClusterCommands ;
3536import io .lettuce .core .codec .ByteArrayCodec ;
36- import java .util .ArrayList ;
37- import java .util .Arrays ;
38- import java .util .List ;
39- import java .util .Map ;
40- import java .util .Optional ;
37+ import java .util .*;
4138import java .util .concurrent .ExecutionException ;
4239import java .util .stream .Collectors ;
40+ import java .util .stream .IntStream ;
41+ import javax .annotation .Nullable ;
4342
4443/** Defines a storage retriever */
4544public class RedisClusterOnlineRetriever implements OnlineRetriever {
4645
4746 private final RedisAdvancedClusterCommands <byte [], byte []> syncCommands ;
47+ private final RedisKeySerializer serializer ;
48+ @ Nullable private final RedisKeySerializer fallbackSerializer ;
4849
49- private RedisClusterOnlineRetriever (StatefulRedisClusterConnection <byte [], byte []> connection ) {
50- this .syncCommands = connection .sync ();
50+ static class Builder {
51+ private final StatefulRedisClusterConnection <byte [], byte []> connection ;
52+ private final RedisKeySerializer serializer ;
53+ @ Nullable private RedisKeySerializer fallbackSerializer ;
54+
55+ Builder (
56+ StatefulRedisClusterConnection <byte [], byte []> connection , RedisKeySerializer serializer ) {
57+ this .connection = connection ;
58+ this .serializer = serializer ;
59+ }
60+
61+ Builder withFallbackSerializer (RedisKeySerializer fallbackSerializer ) {
62+ this .fallbackSerializer = fallbackSerializer ;
63+ return this ;
64+ }
65+
66+ RedisClusterOnlineRetriever build () {
67+ return new RedisClusterOnlineRetriever (this );
68+ }
69+ }
70+
71+ private RedisClusterOnlineRetriever (Builder builder ) {
72+ this .syncCommands = builder .connection .sync ();
73+ this .serializer = builder .serializer ;
74+ this .fallbackSerializer = builder .fallbackSerializer ;
5175 }
5276
5377 public static OnlineRetriever create (Map <String , String > config ) {
@@ -59,15 +83,21 @@ public static OnlineRetriever create(Map<String, String> config) {
5983 return RedisURI .create (hostPortSplit [0 ], Integer .parseInt (hostPortSplit [1 ]));
6084 })
6185 .collect (Collectors .toList ());
62-
6386 StatefulRedisClusterConnection <byte [], byte []> connection =
6487 RedisClusterClient .create (redisURIList ).connect (new ByteArrayCodec ());
6588
66- return new RedisClusterOnlineRetriever (connection );
67- }
89+ RedisKeySerializer serializer =
90+ new RedisKeyPrefixSerializer (config .getOrDefault ("key_prefix" , "" ));
91+
92+ Builder builder = new Builder (connection , serializer );
6893
69- public static OnlineRetriever create (StatefulRedisClusterConnection <byte [], byte []> connection ) {
70- return new RedisClusterOnlineRetriever (connection );
94+ if (Boolean .parseBoolean (config .getOrDefault ("enable_fallback" , "false" ))) {
95+ RedisKeySerializer fallbackSerializer =
96+ new RedisKeyPrefixSerializer (config .getOrDefault ("fallback_prefix" , "" ));
97+ builder = builder .withFallbackSerializer (fallbackSerializer );
98+ }
99+
100+ return builder .build ();
71101 }
72102
73103 /** {@inheritDoc} */
@@ -98,11 +128,9 @@ private List<RedisKey> buildRedisKeys(List<EntityRow> entityRows, FeatureSetSpec
98128 featureSetSpec .getEntitiesList ().stream ()
99129 .map (EntitySpec ::getName )
100130 .collect (Collectors .toList ());
101- List <RedisKey > redisKeys =
102- entityRows .stream ()
103- .map (row -> makeRedisKey (featureSetRef , featureSetEntityNames , row ))
104- .collect (Collectors .toList ());
105- return redisKeys ;
131+ return entityRows .stream ()
132+ .map (row -> makeRedisKey (featureSetRef , featureSetEntityNames , row ))
133+ .collect (Collectors .toList ());
106134 }
107135
108136 /**
@@ -118,9 +146,7 @@ private RedisKey makeRedisKey(
118146 RedisKey .Builder builder = RedisKey .newBuilder ().setFeatureSet (featureSet );
119147 Map <String , Value > fieldsMap = entityRow .getFieldsMap ();
120148 featureSetEntityNames .sort (String ::compareTo );
121- for (int i = 0 ; i < featureSetEntityNames .size (); i ++) {
122- String entityName = featureSetEntityNames .get (i );
123-
149+ for (String entityName : featureSetEntityNames ) {
124150 if (!fieldsMap .containsKey (entityName )) {
125151 throw Status .INVALID_ARGUMENT
126152 .withDescription (
@@ -180,18 +206,59 @@ private List<byte[]> sendMultiGet(List<RedisKey> keys) {
180206 try {
181207 byte [][] binaryKeys =
182208 keys .stream ()
183- .map (AbstractMessageLite :: toByteArray )
209+ .map (serializer :: serialize )
184210 .collect (Collectors .toList ())
185211 .toArray (new byte [0 ][0 ]);
186- return syncCommands .mget (binaryKeys ).stream ()
187- .map (
188- keyValue -> {
189- if (keyValue == null ) {
190- return null ;
191- }
192- return keyValue .getValueOrElse (null );
193- })
194- .collect (Collectors .toList ());
212+ List <byte []> redisValues =
213+ syncCommands .mget (binaryKeys ).stream ()
214+ .map (
215+ keyValue -> {
216+ if (keyValue == null ) {
217+ return null ;
218+ }
219+ return keyValue .getValueOrElse (null );
220+ })
221+ .collect (Collectors .toList ());
222+
223+ List <byte []> redisValuesWithFallback = redisValues ;
224+ if (fallbackSerializer != null ) {
225+ List <Integer > indexMissingValue =
226+ IntStream .range (0 , keys .size ())
227+ .filter (i -> redisValues .get (i ) == null )
228+ .boxed ()
229+ .collect (Collectors .toList ());
230+
231+ byte [][] fallbackBinaryKeys =
232+ indexMissingValue .stream ()
233+ .map (i -> fallbackSerializer .serialize (keys .get (i )))
234+ .collect (Collectors .toList ())
235+ .toArray (new byte [0 ][0 ]);
236+
237+ List <byte []> fallBackValues =
238+ syncCommands .mget (fallbackBinaryKeys ).stream ()
239+ .map (
240+ keyValue -> {
241+ if (keyValue == null ) {
242+ return null ;
243+ }
244+ return keyValue .getValueOrElse (null );
245+ })
246+ .collect (Collectors .toList ());
247+
248+ redisValuesWithFallback =
249+ IntStream .range (0 , keys .size ())
250+ .mapToObj (
251+ i -> {
252+ if (indexMissingValue .contains (i )) {
253+ return fallBackValues .get (indexMissingValue .indexOf (i ));
254+ } else {
255+ return redisValues .get (i );
256+ }
257+ })
258+ .collect (Collectors .toList ());
259+ }
260+
261+ return redisValuesWithFallback ;
195262 } catch (Exception e ) {
196263 throw Status .NOT_FOUND
197264 .withDescription ("Unable to retrieve feature from Redis" )
0 commit comments