The wasmtime runtime allocates memory for the executable code of the WASM programs using mmap and not the seastar allocator. As a result, the memory that Scylla actually uses becomes not only the memory preallocated for the seastar allocator but the sum of that and the memory allocated for executable codes by the WASM runtime. To keep limiting the memory used by Scylla, we measure how much memory do the WASM programs use and if they use too much, compiled WASM UDFs (modules) that are currently not in use are evicted to make room. To evict a module it is required to evict all instances of this module (the underlying implementation of modules and instances uses shared pointers to the executable code). For this reason, we add reference counts to modules. Each instance using a module is a reference. When an instance is destroyed, a reference is removed. If all references to a module are removed, the executable code for this module is deallocated. The eviction of a module is actually acheved by eviction of all its references. When we want to free memory for a new module we repeatedly evict instances from the wasm_instance_cache using its LRU strategy until some module loses all its instances. This process may not succeed if the instances currently in use (so not in the cache) use too much memory - in this case the query also fails. Otherwise the new module is added to the tracking system. This strategy may evict some instances unnecessarily, but evicting modules should not happen frequently, and any more efficient solution requires an even bigger intervention into the code.
90 lines
3.9 KiB
C++
90 lines
3.9 KiB
C++
/*
|
|
* Copyright (C) 2019-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include <seastar/core/coroutine.hh>
|
|
#include "cql3/statements/create_function_statement.hh"
|
|
#include "cql3/functions/functions.hh"
|
|
#include "cql3/functions/user_function.hh"
|
|
#include "prepared_statement.hh"
|
|
#include "service/migration_manager.hh"
|
|
#include "service/storage_proxy.hh"
|
|
#include "lang/lua.hh"
|
|
#include "data_dictionary/data_dictionary.hh"
|
|
#include "replica/database.hh" // for wasm
|
|
#include "cql3/query_processor.hh"
|
|
#include "db/config.hh"
|
|
|
|
namespace cql3 {
|
|
|
|
namespace statements {
|
|
|
|
shared_ptr<functions::function> create_function_statement::create(query_processor& qp, functions::function* old) const {
|
|
if (old && !dynamic_cast<functions::user_function*>(old)) {
|
|
throw exceptions::invalid_request_exception(format("Cannot replace '{}' which is not a user defined function", *old));
|
|
}
|
|
if (_language != "lua" && _language != "xwasm") {
|
|
throw exceptions::invalid_request_exception(format("Language '{}' is not supported", _language));
|
|
}
|
|
data_type return_type = prepare_type(qp, *_return_type);
|
|
std::vector<sstring> arg_names;
|
|
for (const auto& arg_name : _arg_names) {
|
|
arg_names.push_back(arg_name->to_string());
|
|
}
|
|
|
|
auto&& db = qp.db();
|
|
if (_language == "lua") {
|
|
auto cfg = lua::make_runtime_config(db.get_config());
|
|
functions::user_function::context ctx = functions::user_function::lua_context {
|
|
.bitcode = lua::compile(cfg, arg_names, _body),
|
|
.cfg = cfg,
|
|
};
|
|
|
|
return ::make_shared<functions::user_function>(_name, _arg_types, std::move(arg_names), _body, _language,
|
|
std::move(return_type), _called_on_null_input, std::move(ctx));
|
|
} else if (_language == "xwasm") {
|
|
// FIXME: need better way to test wasm compilation without real_database()
|
|
wasm::context ctx{db.real_database().wasm_engine(), _name.name, qp.get_wasm_instance_cache(), db.get_config().wasm_udf_yield_fuel(), db.get_config().wasm_udf_total_fuel()};
|
|
try {
|
|
wasm::precompile(ctx, arg_names, _body);
|
|
return ::make_shared<functions::user_function>(_name, _arg_types, std::move(arg_names), _body, _language,
|
|
std::move(return_type), _called_on_null_input, std::move(ctx));
|
|
} catch (const wasm::exception& we) {
|
|
throw exceptions::invalid_request_exception(we.what());
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<prepared_statement> create_function_statement::prepare(data_dictionary::database db, cql_stats& stats) {
|
|
return std::make_unique<prepared_statement>(make_shared<create_function_statement>(*this));
|
|
}
|
|
|
|
future<std::pair<::shared_ptr<cql_transport::event::schema_change>, std::vector<mutation>>>
|
|
create_function_statement::prepare_schema_mutations(query_processor& qp, api::timestamp_type ts) const {
|
|
::shared_ptr<cql_transport::event::schema_change> ret;
|
|
std::vector<mutation> m;
|
|
|
|
auto func = dynamic_pointer_cast<functions::user_function>(validate_while_executing(qp));
|
|
|
|
if (func) {
|
|
m = co_await qp.get_migration_manager().prepare_new_function_announcement(func, ts);
|
|
ret = create_schema_change(*func, true);
|
|
}
|
|
|
|
co_return std::make_pair(std::move(ret), std::move(m));
|
|
}
|
|
|
|
create_function_statement::create_function_statement(functions::function_name name, sstring language, sstring body,
|
|
std::vector<shared_ptr<column_identifier>> arg_names, std::vector<shared_ptr<cql3_type::raw>> arg_types,
|
|
shared_ptr<cql3_type::raw> return_type, bool called_on_null_input, bool or_replace, bool if_not_exists)
|
|
: create_function_statement_base(std::move(name), std::move(arg_types), or_replace, if_not_exists),
|
|
_language(std::move(language)), _body(std::move(body)), _arg_names(std::move(arg_names)),
|
|
_return_type(std::move(return_type)), _called_on_null_input(called_on_null_input) {}
|
|
}
|
|
}
|