Packages

LowEndInsight is a simple 'bus-factor' risk analysis library for Open Source Software which is managed within a Git repository. Provide the git URL and the library will respond with a basic Elixir Map structure report.

Current section

Files

Jump to
lowendinsight lib cache batch_cache.ex
Raw

lib/cache/batch_cache.ex

defmodule Lei.BatchCache do
@moduledoc """
ETS-backed cache for batch dependency analysis results.
Stores analysis results keyed by {ecosystem, package, version} tuples
for fast parallel lookups during batch SBOM analysis. Designed for
sub-millisecond reads to meet the <500ms target for 50 dependencies.
"""
use GenServer
@table :lei_batch_cache
@default_ttl_seconds 24 * 3600
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Look up a cached analysis result by ecosystem, package, and version.
Returns {:ok, entry} or {:error, :not_found | :expired}.
"""
def get(ecosystem, package, version) do
key = cache_key(ecosystem, package, version)
case :ets.lookup(@table, key) do
[{^key, entry}] ->
now = System.system_time(:second)
if entry.expires_at > now do
{:ok, entry}
else
:ets.delete(@table, key)
{:error, :expired}
end
[] ->
{:error, :not_found}
end
end
@doc """
Store an analysis result in the cache.
"""
def put(ecosystem, package, version, result, opts \\ []) do
key = cache_key(ecosystem, package, version)
ttl = Keyword.get(opts, :ttl, @default_ttl_seconds)
now = System.system_time(:second)
entry = %{
result: result,
cached_at: now,
expires_at: now + ttl,
ecosystem: ecosystem,
package: package,
version: version
}
:ets.insert(@table, {key, entry})
:ok
end
@doc """
Perform parallel cache lookups for a list of dependencies.
Returns a map of %{key => {:ok, entry} | {:error, reason}}.
"""
def multi_get(dependencies) do
dependencies
|> Task.async_stream(
fn dep ->
result = get(dep["ecosystem"], dep["package"], dep["version"])
{cache_key(dep["ecosystem"], dep["package"], dep["version"]), result}
end,
max_concurrency: System.schedulers_online() * 2,
timeout: 5_000
)
|> Enum.reduce(%{}, fn {:ok, {key, result}}, acc ->
Map.put(acc, key, result)
end)
end
@doc """
Returns cache statistics.
"""
def stats do
now = System.system_time(:second)
all =
:ets.tab2list(@table)
|> Enum.filter(fn {_key, entry} -> entry.expires_at > now end)
ecosystems =
all
|> Enum.group_by(fn {_key, entry} -> entry.ecosystem end)
|> Enum.map(fn {eco, items} -> {eco, length(items)} end)
|> Map.new()
%{
count: length(all),
ecosystems: ecosystems
}
end
@doc """
Clear all entries from the batch cache.
"""
def clear do
:ets.delete_all_objects(@table)
:ok
end
# GenServer callbacks
@impl true
def init(_opts) do
table = :ets.new(@table, [:named_table, :public, :set, read_concurrency: true])
{:ok, %{table: table}}
end
defp cache_key(ecosystem, package, version) do
"#{ecosystem}:#{package}:#{version}"
end
end