Невозможно использовать TimescaleDB в тестовой среде Rails

Я застрял в использовании TimescaleDB в Rails - все работает нормально, но в моем наборе тестов я не могу вставить никаких данных.

Что я пробовал

A) Используйте дамп схемы SQL

Это вызывает исходное сообщение об ошибке, которое я видел. Он действительно создает части схемы для TimescaleDB, но не всю ее. У меня есть гипертаблица, но она не работает должным образом

Б) Используйте дамп схемы Ruby

Это позволяет мне вставлять в свою таблицу, но это совсем не гипертаблица - синтаксис ruby ​​теряет все, что связано с TimescaleDB и гипертаблицами.

C) Перенести тестовую базу данных напрямую

Я попытался избежать дампа и загрузки schema.structure следующим образом:

$ rails db:drop
Dropped database 'my_app_development'
Dropped database 'my_app_test'
$ RAILS_ENV=test rails db:create
Created database 'my_app_test'
$ RAILS_ENV=test rails db:migrate
== 20200517164444 EnableTimescaledbExtension: migrating =======================
-- enable_extension("timescaledb")
WARNING:  
WELCOME TO
 _____ _                               _     ____________  
|_   _(_)                             | |    |  _  \ ___ \ 
  | |  _ _ __ ___   ___  ___  ___ __ _| | ___| | | | |_/ / 
  | | | |  _ ` _ \ / _ \/ __|/ __/ _` | |/ _ \ | | | ___ \ 
  | | | | | | | | |  __/\__ \ (_| (_| | |  __/ |/ /| |_/ /
  |_| |_|_| |_| |_|\___||___/\___\__,_|_|\___|___/ \____/
               Running version 1.7.0
For more information on TimescaleDB, please visit the following links:

 1. Getting started: https://docs.timescale.com/getting-started
 2. API reference documentation: https://docs.timescale.com/api
 3. How TimescaleDB is designed: https://docs.timescale.com/introduction/architecture

Note: TimescaleDB collects anonymous reports to better understand and assist our users.
For more information and how to disable, please see our docs https://docs.timescaledb.com/using-timescaledb/telemetry.

   -> 0.2315s
== 20200517164444 EnableTimescaledbExtension: migrated (0.2316s) ==============

== 20200517165027 CreateAccounts: migrating ===================================
-- create_table(:accounts)
   -> 0.0095s
== 20200517165027 CreateAccounts: migrated (0.0095s) ==========================

== 20200517165103 CreateMetrics: migrating ====================================
-- create_table(:metrics)
   -> 0.0116s
== 20200517165103 CreateMetrics: migrated (0.0117s) ===========================

== 20200517170842 CreateEvents: migrating =====================================
-- create_table(:events)
   -> 0.0072s
-- remove_column(:events, :id)
   -> 0.0020s
-- execute("SELECT create_hypertable('events', 'time');\n")
   -> 0.0047s
== 20200517170842 CreateEvents: migrated (0.0142s) ============================

pg_dump: warning: there are circular foreign-key constraints on this table:
pg_dump:   hypertable
pg_dump: You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints.
pg_dump: Consider using a full dump instead of a --data-only dump to avoid this problem.
pg_dump: warning: there are circular foreign-key constraints on this table:
pg_dump:   chunk
pg_dump: You might not be able to restore the dump without using --disable-triggers or temporarily dropping the constraints.
pg_dump: Consider using a full dump instead of a --data-only dump to avoid this problem.

Но при запуске набора тестов это то же самое, что и попытка A.

Выполнение тестов после того, как на самом деле напечатает это сообщение несколько раз, что заставляет меня думать, что Rails автоматически магическим образом снова использует structure.sql для воссоздания тестовой БД:


psql:/home/axel/src/my_app/db/structure.sql:16: WARNING:  
WELCOME TO
 _____ _                               _     ____________  
|_   _(_)                             | |    |  _  \ ___ \ 
  | |  _ _ __ ___   ___  ___  ___ __ _| | ___| | | | |_/ / 
  | | | |  _ ` _ \ / _ \/ __|/ __/ _` | |/ _ \ | | | ___ \ 
  | | | | | | | | |  __/\__ \ (_| (_| | |  __/ |/ /| |_/ /
  |_| |_|_| |_| |_|\___||___/\___\__,_|_|\___|___/ \____/
               Running version 1.7.0
For more information on TimescaleDB, please visit the following links:

 1. Getting started: https://docs.timescale.com/getting-started
 2. API reference documentation: https://docs.timescale.com/api
 3. How TimescaleDB is designed: https://docs.timescale.com/introduction/architecture

Note: TimescaleDB collects anonymous reports to better understand and assist our users.
For more information and how to disable, please see our docs https://docs.timescaledb.com/using-timescaledb/telemetry.

Сообщение об ошибке

$ rails test
Running via Spring preloader in process 107937
Run options: --seed 29840

# Running:

E

Error:
Api::EventsControllerTest#test_POST_event_data_-_new_metric:
DRb::DRbRemoteError: PG::FeatureNotSupported: ERROR:  invalid INSERT on the root table of hypertable "events"
HINT:  Make sure the TimescaleDB extension has been preloaded.
 (ActiveRecord::StatementInvalid)
    app/controllers/api/events_controller.rb:5:in `create'
    test/controllers/api/events_controller_test.rb:9:in `block in <class:EventsControllerTest>'


rails test test/controllers/api/events_controller_test.rb:8



Finished in 0.215286s, 4.6450 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips

У меня такое чувство, что это связано с тем, как Rails создает тестовую базу данных с использованием schema.rb (для config.active_record.schema_format = :ruby по умолчанию) или structure.sql (для config.active_record.schema_format = :sql. Я уже пробовал оба, настройки структуры Ruby и SQL, и ни один из них не работает - база данных разработки переносится правильно, но тестовая БД настроена неправильно.

В двух базах данных ниже (для разработки и тестирования) мы видим единственное различие в том, что тестовая БД отсутствует: Child tables: _timescaledb_internal._hyper_1_1_chunk

БД разработки

$ psql -d my_app_development
psql (12.2)
Type "help" for help.

my_app_development=# SHOW shared_preload_libraries;
 shared_preload_libraries 
--------------------------
 timescaledb
(1 row)

my_app_development=# insert into events (metric_id, time, value) VALUES (1, NOW(), 22);
INSERT 0 1
my_app_development=# \d+ events
                                              Table "public.events"
  Column   |            Type             | Collation | Nullable | Default | Storage | Stats target | Description 
-----------+-----------------------------+-----------+----------+---------+---------+--------------+-------------
 metric_id | bigint                      |           |          |         | plain   |              | 
 time      | timestamp without time zone |           | not null |         | plain   |              | 
 value     | numeric                     |           |          |         | main    |              | 
Indexes:
    "events_time_idx" btree ("time" DESC)
Triggers:
    ts_insert_blocker BEFORE INSERT ON events FOR EACH ROW EXECUTE FUNCTION _timescaledb_internal.insert_blocker()
Child tables: _timescaledb_internal._hyper_1_1_chunk
Access method: heap

Тестовая БД

$ psql -d my_app_test
psql (12.2)
Type "help" for help.

my_app_test=# SHOW shared_preload_libraries;
 shared_preload_libraries 
--------------------------
 timescaledb
(1 row)

my_app_test=# insert into events (metric_id, time, value) VALUES (1, NOW(), 22);
ERROR:  invalid INSERT on the root table of hypertable "events"
HINT:  Make sure the TimescaleDB extension has been preloaded.
my_app_test=# \d+ events
                                              Table "public.events"
  Column   |            Type             | Collation | Nullable | Default | Storage | Stats target | Description 
-----------+-----------------------------+-----------+----------+---------+---------+--------------+-------------
 metric_id | bigint                      |           |          |         | plain   |              | 
 time      | timestamp without time zone |           | not null |         | plain   |              | 
 value     | numeric                     |           |          |         | main    |              | 
Indexes:
    "events_time_idx" btree ("time" DESC)
Triggers:
    ts_insert_blocker BEFORE INSERT ON events FOR EACH ROW EXECUTE FUNCTION _timescaledb_internal.insert_blocker()
Access method: heap

ActiveRecord со схемой SQL

CREATE EXTENSION IF NOT EXISTS timescaledb WITH SCHEMA public;

SET default_tablespace = '';

SET default_table_access_method = heap;

CREATE TABLE public.events (
    metric_id bigint,
    "time" timestamp without time zone NOT NULL,
    value numeric
);

CREATE INDEX events_time_idx ON public.events USING btree ("time" DESC);

CREATE TRIGGER ts_insert_blocker BEFORE INSERT ON public.events FOR EACH ROW EXECUTE FUNCTION _timescaledb_internal.insert_blocker();

ActiveRecord со схемой Ruby

ActiveRecord::Schema.define(version: 2020_05test/test_helper.rb170842) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"
  enable_extension "timescaledb"

  create_table "events", id: false, force: :cascade do |t|
    t.bigint "metric_id"
    t.datetime "time", null: false
    t.decimal "value"
    t.index ["time"], name: "events_time_idx", order: :desc
  end
end

Примечание: это теряет триггер ts_insert_blocker и позволяет мне вставить в таблицу events, но это больше не гипертаблица:


my_app_test=# \d+ events
                                              Table "public.events"
  Column   |            Type             | Collation | Nullable | Default | Storage | Stats target | Description 
-----------+-----------------------------+-----------+----------+---------+---------+--------------+-------------
 metric_id | bigint                      |           |          |         | plain   |              | 
 time      | timestamp without time zone |           | not null |         | plain   |              | 
 value     | numeric                     |           |          |         | main    |              | 
Indexes:
    "events_time_idx" btree ("time" DESC)
Access method: heap

Дополнительная информация

Связанный вопрос: Запуск RSpec набор тестов для базы данных TimescaleDB с Rails 4.2 - предложения не сработали для меня, и нет принятого ответа.

Информация о версии:

  • Рельсы 6.0.3
  • Postgres 12.2
  • Шкала времениDB 1.7.0

Редактировать 1

Я добавил в свой test/test_helper.rb следующее, похожее на обходной путь, упомянутый @cstabru

def execute_create_hypertable(sql)
  ActiveRecord::Base.connection.execute(sql)
rescue ActiveRecord::StatementInvalid => e
  raise e unless e.message.include? 'is already a hypertable'
end

execute_create_hypertable <<~SQL
  SELECT create_hypertable('events', 'time');
SQL

Но, может быть, мы можем использовать что-то вроде SELECT create_hypertable('hypertable_name', 'time_field', if_not_exists => TRUE в инициализаторе вместо создания гипертаблиц при миграции БД?


person AxelTheGerman    schedule 18.05.2020    source источник


Ответы (1)


Я тоже столкнулся с этим, независимо от того, каким образом я воссоздаю схему db (форматы sql или ruby), гипер-таблица не воссоздается, поскольку данные внутренней схемы шкалы времени не экспортируются.

Отмечая, что при восстановлении с использованием формата sql он копируется через триггер ts_insert_blocker, который действительно нарушает вставки в таблице с этой ошибкой (я считаю, что это связано с недоступностью функции триггера)

PG::FeatureNotSupported: ERROR:  invalid INSERT on the root table of hypertable "hypertable_name"
HINT:  Make sure the TimescaleDB extension has been preloaded.

Чтобы исправить основную проблему (форматы sql или ruby), мы можем воссоздать гипертаблицу (и удалить триггер) вручную, выполнив следующие действия.

DROP TRIGGER IF EXISTS ts_insert_blocker ON events;
DROP TRIGGER

SELECT create_hypertable('hypertable_name', 'time_field', if_not_exists => TRUE);
....
(1 row)

Теперь вручную проверьте наличие гипертаблицы, поскольку https://github.com/timescale/timescaledb/pull/862

SELECT * FROM timescaledb_information.hypertable;

Я добавил эти DDL-команды в свой spec_helper.rb, чтобы убедиться, что тестовая база данных использует настоящую гипертаблицу. Я хочу убедиться, что схема тестовой базы данных отражает мои производственные / промежуточные настройки.

config.before(:suite) do
  # ensure the hypertable_name hypertable is setup correctly
  ActiveRecord::Base.connection.execute(
    "DROP TRIGGER IF EXISTS ts_insert_blocker ON hypertable_name;"
  )
  ActiveRecord::Base.connection.execute(
    "SELECT create_hypertable('hypertable_name', 'time_field', if_not_exists => TRUE);"
  )
  has_hypertables_sql = "SELECT * FROM timescaledb_information.hypertable WHERE table_name = 'hypertable_name';"
  if ActiveRecord::Base.connection.execute(has_hypertables_sql).to_a.empty?
    raise "TimescaleDB missing hypertable on 'hypertable_name' table"
  end
end

Если люди сочтут это полезным, я могу посмотреть на извлечение в гем, чтобы помочь с восстановлением схемы для сред rails, https://github.com/timescale/timescaledb/issues/1916

person cstabru    schedule 22.05.2020
comment
Благодарность! Я пришел к аналогичному обходному пути, добавив некоторый код прямо в мой test_helper.rb - добавлю к моему вопросу. Я просто думал то же самое, что это могло бы быть полезно для добычи как драгоценный камень. Кроме того, мне нравится create_hypertable ... if_not_exists Может быть, наличие этого кода в инициализаторе вместо миграции просто позаботится обо всех средах? - person AxelTheGerman; 24.05.2020
comment
также у меня не было проблем с ts_insert_blocker, когда я правильно создал гипертаблицу - person AxelTheGerman; 24.05.2020
comment
Рад, что он работает, и приятно узнать о ts_insert_blocker, я предполагаю, что воссоздание гипертаблицы добавляет функцию триггера, поэтому она работает. - person cstabru; 27.05.2020
comment
да, я действительно обнаружил, что при использовании schema.rb проблем с ts_insert_blocker нет, поскольку оно исходит не из вашего приложения. Но при использовании structure.sql он будет включать ts_insert_blocker, что нехорошо. В этом случае, как вы упомянули, просто отбросьте его, и повторное создание гипертаблицы снова добавит этот триггер :) - person AxelTheGerman; 30.05.2020