diff --git a/lib/sanbase/accounts/apikey/apikey.ex b/lib/sanbase/accounts/apikey/apikey.ex index 10eaca6e30..4ff20b665a 100644 --- a/lib/sanbase/accounts/apikey/apikey.ex +++ b/lib/sanbase/accounts/apikey/apikey.ex @@ -25,11 +25,13 @@ defmodule Sanbase.Accounts.Apikey do user tokens for that ID and check if the apikey is generated from any of the tokens """ + @apikey_user_cache_ttl 60 + @spec apikey_to_user(String.t()) :: {:ok, %User{}} | {:error, String.t()} def apikey_to_user(apikey) do with {:ok, {token, _rest}} <- Hmac.split_apikey(apikey), {_, true} <- {:valid?, Hmac.apikey_valid?(token, apikey)}, - {_, {:ok, user}} <- {:user?, User.by_apikey_token(token)} do + {_, {:ok, user}} <- {:user?, fetch_user_by_token_cached(token)} do {:ok, user} else {:valid?, _} -> @@ -43,6 +45,16 @@ defmodule Sanbase.Accounts.Apikey do end end + defp fetch_user_by_token_cached(token) do + cache_key = + {__MODULE__, :apikey_user, token} + |> Sanbase.Cache.hash() + + Sanbase.Cache.get_or_store({cache_key, @apikey_user_cache_ttl}, fn -> + User.by_apikey_token(token) + end) + end + def mask_apikey(apikey) do apikey_length = String.length(apikey) # All between the first 6 chars and the last 2 chars will be hidden diff --git a/lib/sanbase/cache/cache.ex b/lib/sanbase/cache/cache.ex index f58133a02b..9dc08404a6 100644 --- a/lib/sanbase/cache/cache.ex +++ b/lib/sanbase/cache/cache.ex @@ -3,6 +3,9 @@ defmodule Sanbase.Cache do @cache_name :sanbase_cache @max_cache_ttl 86_400 + # This is 2^32, so the hashing functions return everything in 0..2^32 range + @phash_upper_bound 4_294_967_296 + @compile {:inline, get_or_store_isolated: 4} @impl Sanbase.Cache.Behaviour @@ -21,8 +24,13 @@ defmodule Sanbase.Cache do @impl Sanbase.Cache.Behaviour def hash(data) do - :crypto.hash(:sha256, :erlang.term_to_binary(data)) - |> Base.encode64() + # Use two phash2 calls for a 64-bit hash space (negligible collision risk) + # instead of SHA256 + term_to_binary + Base64. phash2 operates directly on + # Erlang terms without serialization, making it orders of magnitude faster. + # Slightly change the input to h2 so it returns a different result + h1 = :erlang.phash2(data, @phash_upper_bound) + h2 = :erlang.phash2({:salted, data}, @phash_upper_bound) + <> |> Base.encode16(case: :lower) end def name, do: @cache_name diff --git a/lib/sanbase/clickhouse_repo.ex b/lib/sanbase/clickhouse_repo.ex index 3e82ec7283..de144ca9da 100644 --- a/lib/sanbase/clickhouse_repo.ex +++ b/lib/sanbase/clickhouse_repo.ex @@ -54,25 +54,40 @@ defmodule Sanbase.ClickhouseRepo do end end + @include_stacktrace_in_ch_queries System.get_env("CLICKHOUSE_INCLUDE_STACKTRACE", "false") == + "true" + defp add_metadata_to_query(query) do type = System.get_env("CONTAINER_TYPE") || "all" request_id = (Process.get(:"$logger_metadata$") || %{}) |> Map.get(:request_id) - {_, [_process_info_call | rest_stacktrace]} = Process.info(self(), :current_stacktrace) - - stacktrace = - Enum.take(rest_stacktrace, 5) |> :erlang.term_to_binary() |> :zlib.gzip() |> Base.encode64() - query - |> Sanbase.Clickhouse.Query.add_leading_comment("sanbase_container_type #{type}") - |> Sanbase.Clickhouse.Query.extend_log_comment(%{ + log_comment = %{ sanbase_container_type: type, owner: "backend", team: "backend", repo: "sanbase2", - graphql_request_log_id: request_id, - stacktrace: stacktrace - }) + graphql_request_log_id: request_id + } + + log_comment = + if @include_stacktrace_in_ch_queries do + {_, [_process_info_call | rest_stacktrace]} = Process.info(self(), :current_stacktrace) + + stacktrace = + Enum.take(rest_stacktrace, 5) + |> :erlang.term_to_binary() + |> :zlib.gzip() + |> Base.encode64() + + Map.put(log_comment, :stacktrace, stacktrace) + else + log_comment + end + + query + |> Sanbase.Clickhouse.Query.add_leading_comment("sanbase_container_type #{type}") + |> Sanbase.Clickhouse.Query.extend_log_comment(log_comment) end def query_transform(query, args, transform_fn) do diff --git a/lib/sanbase_web/graphql/cache/cachex_provider.ex b/lib/sanbase_web/graphql/cache/cachex_provider.ex index 4dd823a5fd..40bfa26124 100644 --- a/lib/sanbase_web/graphql/cache/cachex_provider.ex +++ b/lib/sanbase_web/graphql/cache/cachex_provider.ex @@ -195,14 +195,13 @@ defmodule SanbaseWeb.Graphql.CachexProvider do defp true_key(key), do: key defp compress_value(value) do - value - |> :erlang.term_to_binary() - |> :zlib.gzip() + # Use Erlang's built-in compressed binary format instead of gzip. + # This is ~3-5x faster than term_to_binary + zlib.gzip while still + # reducing memory usage. Level 1 gives best speed/size tradeoff. + :erlang.term_to_binary(value, compressed: 1) end defp decompress_value(value) do - value - |> :zlib.gunzip() - |> :erlang.binary_to_term() + :erlang.binary_to_term(value) end end