diff --git a/sbroad-cartridge/src/api.rs b/sbroad-cartridge/src/api.rs
index 82db1e0cbfc6f64118e120c7e6d720767f3b4d4d..cbc1a7786ae855e399d27ccc16b7c451b893d85a 100644
--- a/sbroad-cartridge/src/api.rs
+++ b/sbroad-cartridge/src/api.rs
@@ -9,3 +9,4 @@ pub mod calculate_bucket_id;
 pub mod exec_query;
 mod helper;
 pub mod invalidate_cached_schema;
+pub mod statistics;
diff --git a/sbroad-cartridge/src/api/statistics.rs b/sbroad-cartridge/src/api/statistics.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0148c616899177d6cf97ca39d00f1d9d0d9af672
--- /dev/null
+++ b/sbroad-cartridge/src/api/statistics.rs
@@ -0,0 +1,21 @@
+use sbroad::debug;
+use sbroad::otm::statistics::table::{QUERY, SPAN, STAT};
+use std::os::raw::c_int;
+
+/// Initialize query statistics tables.
+///
+/// Though the function always returns a success, it can fail to create
+/// the table (for example on read-only replica). In this case, the
+/// warning will be logged, but the function will return success.
+#[allow(clippy::module_name_repetitions)]
+#[no_mangle]
+pub extern "C" fn init_statistics() -> c_int {
+    debug!(
+        Option::from("init_statistics"),
+        "Initializing statistics tables"
+    );
+    QUERY.with(|_| {});
+    SPAN.with(|_| {});
+    STAT.with(|_| {});
+    0
+}
diff --git a/sbroad-cartridge/src/router.lua b/sbroad-cartridge/src/router.lua
index 00a069296c7bd50ef13b4afb86babc672e20837c..be99180ca8850009338b9883ae3a87a25f6d6fc2 100644
--- a/sbroad-cartridge/src/router.lua
+++ b/sbroad-cartridge/src/router.lua
@@ -217,6 +217,13 @@ local function init()
         'libsbroad.dispatch_query',
         { if_not_exists = true, language = 'C' }
     )
+
+    box.schema.func.create(
+        'libsbroad.init_statistics',
+        { if_not_exists = true, language = 'C' }
+    )
+
+    box.func["libsbroad.init_statistics"]:call({})
 end
 
 local function calculate_bucket_id(values, space_name) -- luacheck: no unused args
diff --git a/sbroad-cartridge/src/storage.lua b/sbroad-cartridge/src/storage.lua
index a3d3bd890b4dc59795ddd06a0af562ff3549cc25..3eeca2a99bc2bb00ac0d71f617bac56042cc306a 100644
--- a/sbroad-cartridge/src/storage.lua
+++ b/sbroad-cartridge/src/storage.lua
@@ -87,6 +87,13 @@ local function init()
         'libsbroad.invalidate_segment_cache',
         { if_not_exists = true, language = 'C' }
     )
+
+    box.schema.func.create(
+        'libsbroad.init_statistics',
+        { if_not_exists = true, language = 'C' }
+    )
+
+    box.func["libsbroad.init_statistics"]:call({})
 end
 
 local function invalidate_cache()
diff --git a/sbroad-core/src/otm/statistics/table.rs b/sbroad-core/src/otm/statistics/table.rs
index f2a1e6ed98ae1990509d11350c723bbf0e724f9b..47a68533bb370ef9e9aa8934a4a03903dd1937ba 100644
--- a/sbroad-core/src/otm/statistics/table.rs
+++ b/sbroad-core/src/otm/statistics/table.rs
@@ -33,9 +33,9 @@ use tarantool::{index, space};
 
 use crate::{debug, warn};
 
-thread_local!(pub(super) static QUERY: RefCell<QuerySpace> = RefCell::new(QuerySpace::new()));
-thread_local!(pub(super) static SPAN: RefCell<SpanMap> = RefCell::new(SpanMap::new()));
-thread_local!(pub(super) static STAT: RefCell<StatSpace> = RefCell::new(StatSpace::new()));
+thread_local!(pub static QUERY: RefCell<QuerySpace> = RefCell::new(QuerySpace::new()));
+thread_local!(pub static SPAN: RefCell<SpanMap> = RefCell::new(SpanMap::new()));
+thread_local!(pub static STAT: RefCell<StatSpace> = RefCell::new(StatSpace::new()));
 
 pub trait RustMap {
     type Key;