feat(Database::Videos): built-in video cache and support for multiple caching backends
Some checks failed
Invidious CI / build (push) Failing after 38s
Some checks failed
Invidious CI / build (push) Failing after 38s
I did this to get rid of Redis compatible DBs and for speed purposes. This is considered experimental, but everything works fine from what I have tested. Here are some benchmarks using the built-in benchmark library of crystal: \# built-in release cache get 19.79M ( 50.54ns) (± 4.12%) 32.0B/op fastest cache insert 7.88k (126.86µs) (± 2.20%) 65.5kB/op fastest cache get 4.31k (232.11µs) (± 5.50%) 104kB/op fastest \# redis release cache get 22.27k ( 44.90µs) (± 6.40%) 264B/op fastest cache insert 4.74k (211.01µs) (± 4.72%) 65.7kB/op fastest cache get 2.51k (399.11µs) (±13.15%) 129kB/op fastest --- OP/s are way higher, and memory usage per call is lower, so it's a win win.
This commit is contained in:
parent
62cc10d2ca
commit
e76867aaba
3 changed files with 164 additions and 46 deletions
|
@ -156,19 +156,6 @@ end
|
||||||
OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a")
|
OUTPUT = CONFIG.output.upcase == "STDOUT" ? STDOUT : File.open(CONFIG.output, mode: "a")
|
||||||
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level, CONFIG.colorize_logs)
|
LOGGER = Invidious::LogHandler.new(OUTPUT, CONFIG.log_level, CONFIG.colorize_logs)
|
||||||
|
|
||||||
REDIS_DB = begin
|
|
||||||
LOGGER.info "Connecting to Redis compatible DB"
|
|
||||||
redis = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil)
|
|
||||||
if redis.ping
|
|
||||||
LOGGER.info "Connected to Redis compatible DB via unix domain socket at '#{CONFIG.redis_socket}'" if CONFIG.redis_socket
|
|
||||||
LOGGER.info "Connected to Redis compatible DB via TCP socket at '#{CONFIG.redis_url}'" if CONFIG.redis_url
|
|
||||||
end
|
|
||||||
redis
|
|
||||||
rescue ex
|
|
||||||
LOGGER.error "Failed to connect to a Redis compatible DB. Invidious will store the video cache on the PostgresSQL DB"
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check table integrity
|
# Check table integrity
|
||||||
Invidious::Database.check_integrity(CONFIG)
|
Invidious::Database.check_integrity(CONFIG)
|
||||||
|
|
||||||
|
|
|
@ -217,7 +217,16 @@ class Config
|
||||||
|
|
||||||
property tokens_server : String = ""
|
property tokens_server : String = ""
|
||||||
|
|
||||||
property video_cache : Bool = true
|
property video_cache : VideoCacheConfig
|
||||||
|
|
||||||
|
class VideoCacheConfig
|
||||||
|
include YAML::Serializable
|
||||||
|
|
||||||
|
property enabled : Bool = true
|
||||||
|
property backend : Int32 = 1
|
||||||
|
# Max quantity of keys that can be held on the LRU cache
|
||||||
|
property lru_max_size : Int32 = 18432 # ~512MB
|
||||||
|
end
|
||||||
|
|
||||||
{% if flag?(:linux) %}
|
{% if flag?(:linux) %}
|
||||||
property reload_config_automatically : Bool = true
|
property reload_config_automatically : Bool = true
|
||||||
|
@ -395,6 +404,16 @@ class Config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if config.video_cache.enabled
|
||||||
|
if !config.video_cache.backend.in?(0, 1, 2)
|
||||||
|
puts "Config: 'video_cache_storage', can only be:"
|
||||||
|
puts "0 (PostgreSQL)"
|
||||||
|
puts "1 (Redis compatible DB) (Default)"
|
||||||
|
puts "2 (In memory LRU)"
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return config
|
return config
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,42 +1,63 @@
|
||||||
require "./base.cr"
|
require "./base.cr"
|
||||||
|
require "redis"
|
||||||
|
|
||||||
|
VideoCache = Invidious::Database::Videos::Cache.new
|
||||||
|
|
||||||
module Invidious::Database::Videos
|
module Invidious::Database::Videos
|
||||||
module DBCache
|
class Cache
|
||||||
extend self
|
def initialize
|
||||||
|
case CONFIG.video_cache.backend
|
||||||
|
when 0
|
||||||
|
@cache = CacheMethods::PostgresSQL.new
|
||||||
|
when 1
|
||||||
|
@cache = CacheMethods::Redis_.new
|
||||||
|
when 2
|
||||||
|
@cache = CacheMethods::LRU.new
|
||||||
|
else
|
||||||
|
LOGGER.debug "Video Cache: Using default cache method to store video cache (PostgreSQL)"
|
||||||
|
@cache = CacheMethods::PostgresSQL.new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def set(video : Video, expire_time)
|
def set(video : Video, expire_time)
|
||||||
if redis = REDIS_DB
|
@cache.set(video, expire_time)
|
||||||
redis.set(video.id, video.info.to_json, expire_time)
|
|
||||||
redis.set(video.id + ":time", video.updated, expire_time)
|
|
||||||
else
|
|
||||||
request = <<-SQL
|
|
||||||
INSERT INTO videos
|
|
||||||
VALUES ($1, $2, $3)
|
|
||||||
ON CONFLICT (id) DO NOTHING
|
|
||||||
SQL
|
|
||||||
|
|
||||||
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def del(id : String)
|
def del(id : String)
|
||||||
if redis = REDIS_DB
|
@cache.del(id)
|
||||||
redis.del(id)
|
|
||||||
redis.del(id + ":time")
|
|
||||||
else
|
|
||||||
request = <<-SQL
|
|
||||||
DELETE FROM videos *
|
|
||||||
WHERE id = $1
|
|
||||||
SQL
|
|
||||||
|
|
||||||
PG_DB.exec(request, id)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(id : String)
|
def get(id : String)
|
||||||
if redis = REDIS_DB
|
return @cache.get(id)
|
||||||
info = redis.get(id)
|
end
|
||||||
time = redis.get(id + ":time")
|
end
|
||||||
|
|
||||||
|
module CacheMethods
|
||||||
|
# TODO: Save the cache on a file with a Job
|
||||||
|
class LRU
|
||||||
|
@max_size : Int32
|
||||||
|
@lru = {} of String => String
|
||||||
|
@access = [] of String
|
||||||
|
|
||||||
|
def initialize(@max_size = CONFIG.video_cache.lru_max_size)
|
||||||
|
LOGGER.info "Video Cache: Using in memory LRU to store video cache"
|
||||||
|
LOGGER.info "Video Cache, LRU: LRU cache max size set to #{@max_size}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Handle expire_time with a Job
|
||||||
|
def set(video : Video, expire_time)
|
||||||
|
self[video.id] = video.info.to_json
|
||||||
|
self[video.id + ":time"] = "#{video.updated}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def del(id : String)
|
||||||
|
self.delete(id)
|
||||||
|
self.delete(id + ":time")
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(id : String)
|
||||||
|
info = self[id]
|
||||||
|
time = self[id + ":time"]
|
||||||
if info && time
|
if info && time
|
||||||
return Video.new({
|
return Video.new({
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -46,7 +67,98 @@ module Invidious::Database::Videos
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
else
|
end
|
||||||
|
|
||||||
|
private def [](key)
|
||||||
|
if @lru[key]?
|
||||||
|
@access.delete(key)
|
||||||
|
@access.push(key)
|
||||||
|
@lru[key]
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private def []=(key, value)
|
||||||
|
if @lru.size >= @max_size
|
||||||
|
lru_key = @access.shift
|
||||||
|
@lru.delete(lru_key)
|
||||||
|
end
|
||||||
|
@lru[key] = value
|
||||||
|
@access.push(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
private def delete(key)
|
||||||
|
if @lru[key]?
|
||||||
|
@lru.delete(key)
|
||||||
|
@access.delete(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Redis_
|
||||||
|
@redis : Redis::PooledClient
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@redis = Redis::PooledClient.new(unixsocket: CONFIG.redis_socket || nil, url: CONFIG.redis_url || nil)
|
||||||
|
LOGGER.info "Video Cache: Using Redis compatible DB to store video cache"
|
||||||
|
LOGGER.info "Connecting to Redis compatible DB"
|
||||||
|
if @redis.ping
|
||||||
|
LOGGER.info "Connected to Redis compatible DB via unix domain socket at '#{CONFIG.redis_socket}'" if CONFIG.redis_socket
|
||||||
|
LOGGER.info "Connected to Redis compatible DB via TCP socket at '#{CONFIG.redis_url}'" if CONFIG.redis_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(video : Video, expire_time)
|
||||||
|
@redis.set(video.id, video.info.to_json, expire_time)
|
||||||
|
@redis.set(video.id + ":time", video.updated, expire_time)
|
||||||
|
end
|
||||||
|
|
||||||
|
def del(id : String)
|
||||||
|
@redis.del(id)
|
||||||
|
@redis.del(id + ":time")
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(id : String)
|
||||||
|
info = @redis.get(id)
|
||||||
|
time = @redis.get(id + ":time")
|
||||||
|
if info && time
|
||||||
|
return Video.new({
|
||||||
|
id: id,
|
||||||
|
info: JSON.parse(info).as_h,
|
||||||
|
updated: Time.parse(time, "%Y-%m-%d %H:%M:%S %z", Time::Location::UTC),
|
||||||
|
})
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class PostgresSQL
|
||||||
|
def initialize
|
||||||
|
LOGGER.info "Video Cache: Using PostgreSQL to store video cache"
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(video : Video, expire_time)
|
||||||
|
request = <<-SQL
|
||||||
|
INSERT INTO videos
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (id) DO NOTHING
|
||||||
|
SQL
|
||||||
|
|
||||||
|
PG_DB.exec(request, video.id, video.info.to_json, video.updated)
|
||||||
|
end
|
||||||
|
|
||||||
|
def del(id)
|
||||||
|
request = <<-SQL
|
||||||
|
DELETE FROM videos *
|
||||||
|
WHERE id = $1
|
||||||
|
SQL
|
||||||
|
|
||||||
|
PG_DB.exec(request, id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(id : String) : Video?
|
||||||
request = <<-SQL
|
request = <<-SQL
|
||||||
SELECT * FROM videos
|
SELECT * FROM videos
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
|
@ -60,11 +172,11 @@ module Invidious::Database::Videos
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
def insert(video : Video)
|
def insert(video : Video)
|
||||||
DBCache.set(video: video, expire_time: 14400) if CONFIG.video_cache
|
VideoCache.set(video: video, expire_time: 14400) if CONFIG.video_cache.enabled
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(id)
|
def delete(id)
|
||||||
DBCache.del(id)
|
VideoCache.del(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_expired
|
def delete_expired
|
||||||
|
@ -87,6 +199,6 @@ module Invidious::Database::Videos
|
||||||
end
|
end
|
||||||
|
|
||||||
def select(id : String) : Video?
|
def select(id : String) : Video?
|
||||||
return DBCache.get(id)
|
return VideoCache.get(id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue