K/V Database Integration
View SourceFAISS indexes can be serialized to binary for storage in any key-value database.
Serialize and Deserialize
%% Create and populate an index
{ok, Index} = barrel_faiss:new(128),
ok = barrel_faiss:add(Index, Vectors),
%% Serialize to binary
{ok, Binary} = barrel_faiss:serialize(Index),
%% Deserialize back to index
{ok, RestoredIndex} = barrel_faiss:deserialize(Binary).RocksDB Integration
Store FAISS indexes in RocksDB:
-module(vector_store).
-export([save_index/3, load_index/2, search/4]).
save_index(Db, Name, Index) ->
{ok, Binary} = barrel_faiss:serialize(Index),
Key = <<"barrel_faiss:", Name/binary>>,
rocksdb:put(Db, Key, Binary, []).
load_index(Db, Name) ->
Key = <<"barrel_faiss:", Name/binary>>,
case rocksdb:get(Db, Key, []) of
{ok, Binary} -> barrel_faiss:deserialize(Binary);
not_found -> {error, not_found}
end.
search(Db, Name, Query, K) ->
case load_index(Db, Name) of
{ok, Index} ->
Result = barrel_faiss:search(Index, Query, K),
barrel_faiss:close(Index),
Result;
Error ->
Error
end.ETS Cache with Persistence
Cache indexes in ETS, persist to disk:
-module(vector_cache).
-behaviour(gen_server).
-export([start_link/1, get_or_load/2, save/2]).
-export([init/1, handle_call/3, handle_cast/2, terminate/2]).
start_link(Dir) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Dir, []).
init(Dir) ->
Tab = ets:new(?MODULE, [set, protected, {keypos, 1}]),
{ok, #{table => Tab, dir => Dir}}.
get_or_load(Name, Dimension) ->
gen_server:call(?MODULE, {get_or_load, Name, Dimension}).
save(Name, Index) ->
gen_server:call(?MODULE, {save, Name, Index}).
handle_call({get_or_load, Name, Dimension}, _From, State) ->
#{table := Tab, dir := Dir} = State,
case ets:lookup(Tab, Name) of
[{Name, Index}] ->
{reply, {ok, Index}, State};
[] ->
Path = filename:join(Dir, <<Name/binary, ".faiss">>),
case barrel_faiss:read_index(Path) of
{ok, Index} ->
ets:insert(Tab, {Name, Index}),
{reply, {ok, Index}, State};
{error, _} ->
{ok, Index} = barrel_faiss:new(Dimension),
ets:insert(Tab, {Name, Index}),
{reply, {ok, Index}, State}
end
end;
handle_call({save, Name, Index}, _From, State) ->
#{dir := Dir} = State,
Path = filename:join(Dir, <<Name/binary, ".faiss">>),
Result = barrel_faiss:write_index(Index, Path),
{reply, Result, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
terminate(_Reason, State) ->
#{table := Tab} = State,
ets:foldl(fun({_Name, Index}, _) -> barrel_faiss:close(Index) end, ok, Tab),
ok.Sharded Index Pattern
For large vector sets, shard across multiple indexes:
-module(sharded_index).
-export([new/2, add/3, search/3]).
-record(sharded, {
dimension :: pos_integer(),
shards :: #{non_neg_integer() => barrel_faiss:index()},
num_shards :: pos_integer()
}).
new(Dimension, NumShards) ->
Shards = maps:from_list([
{I, element(2, barrel_faiss:new(Dimension))}
|| I <- lists:seq(0, NumShards - 1)
]),
#sharded{dimension = Dimension, shards = Shards, num_shards = NumShards}.
add(#sharded{shards = Shards, num_shards = N} = S, VectorId, Vector) ->
ShardId = VectorId rem N,
Index = maps:get(ShardId, Shards),
ok = barrel_faiss:add(Index, Vector),
S.
search(#sharded{shards = Shards}, Query, K) ->
%% Search all shards in parallel
Results = pmap(
fun({_Id, Index}) ->
barrel_faiss:search(Index, Query, K)
end,
maps:to_list(Shards)
),
%% Merge and sort results
merge_results(Results, K).
pmap(Fun, List) ->
Parent = self(),
Pids = [spawn_link(fun() -> Parent ! {self(), Fun(X)} end) || X <- List],
[receive {Pid, R} -> R end || Pid <- Pids].
merge_results(Results, K) ->
%% Combine all distances and labels, sort by distance, take top K
AllPairs = lists:flatmap(
fun({ok, Distances, Labels}) ->
Ds = [D || <<D:32/float-native>> <= Distances],
Ls = [L || <<L:64/signed-native>> <= Labels],
lists:zip(Ds, Ls)
end,
Results
),
Sorted = lists:sort(fun({D1, _}, {D2, _}) -> D1 =< D2 end, AllPairs),
TopK = lists:sublist(Sorted, K),
{Ds, Ls} = lists:unzip(TopK),
DistBin = << <<D:32/float-native>> || D <- Ds >>,
LabelBin = << <<L:64/signed-native>> || L <- Ls >>,
{ok, DistBin, LabelBin}.Best Practices
- Batch operations - Add vectors in batches for better performance
- Use HNSW for search-heavy workloads - Fast approximate search
- Use IVF for large indexes - Better memory efficiency
- Cache deserialized indexes - Avoid repeated deserialization
- Close indexes when done - Release native resources