Ошибка обновления - rake db:migrate индекс по theme_field_id

В последних заметках о выпуске (3.2.0.beta1) я обратил внимание на плагин discourse-ai, который ранее не встречал, поэтому попытался добавить его и одновременно обновить свой экземпляр Discourse.

Как указано в заголовке, в процессе инициализации (bootstrap) возникает ошибка: команда rake db:migrate не может создать уникальный индекс по полю theme_field_id. Ниже приведены детали того, как я пришёл к этой точке…

Первая попытка обновления (ошибка patch-package)

Я использую установку с разделёнными контейнерами, поэтому:

  • Отредактировал файл web_only.yml, добавив новый плагин discourse-ai.

    Например, добавлена дополнительная строка в секцию hooks плагинов
    ## Плагины размещаются здесь
    ## подробности см. на https://meta.discourse.org/t/19157
    hooks:
      after_code:
        - exec:
            cd: $home/plugins
            cmd:
              - sudo -E -u discourse git clone https://github.com/discourse/docker_manager.git
              - sudo -E -u discourse git clone https://github.com/discourse/discourse-voting.git
              - sudo -E -u discourse git clone https://github.com/discourse/discourse-ai.git
    
  • Запустил ./launcher bootstrap web_only.

Процесс завершился ошибкой с сообщением о том, что patch-package не найден.

Git pull → bootstrap (ошибка pg-vector)

Я решил убедиться, что у меня установлены последние обновления лаунчера, прежде чем повторять попытку:

  • Выполнил git pull, чтобы убедиться, что у меня есть последние обновления, связанные с лаунчером.
  • Снова запустил ./launcher bootstrap web_only.

На этот раз появились сообщения об ошибках, связанные с pg-vector.

:page_facing_up: Фрагмент лога из bootstrap *с* discourse-ai

Я записал версии PostgreSQL, чтобы иметь их в своём отчёте, если решу вернуться к плагину discourse-ai.

  • web_only:
    • клиент: psql (PostgreSQL) 13.10 (Debian 13.10-1.pgdg110+1)
  • data:
    • сервер: PostgreSQL 13.9 (Debian 13.9-1.pgdg110+1)

Удаление плагина discourse-ai → bootstrap

Затем я удалил плагин discourse-ai из файла web_only.yml и снова запустил bootstrap.

К моему удивлению, ошибки всё ещё присутствовали, но на этот раз они, казалось, были связаны с невозможностью rake db:migrate создать уникальный индекс index_javascript_caches_on_theme_field_id с деталями: ключ (theme_field_id)=(3) дублируется.

:page_facing_up: Фрагмент лога из bootstrap *без* discourse-ai

Ваша помощь? :folded_hands:

Это привело меня сюда в поисках помощи. Я решил сделать паузу и получить мнения сообщества, прежде чем углубляться дальше, на случай, если кто-то уже сталкивался с этим.

Для справки: у меня установлена версия 3.2.0.beta1-dev (993ed10cf0 ~ 9 августа).

Хотя я не думаю, что это связано с данной проблемой, считаю, что не помешает упомянуть, что в начале этого года я мигрировал между хост-машинами… Хотя с тех пор я выполнил несколько обновлений Discourse через административный интерфейс.

Подход к миграции

По памяти, это было примерно следующее: обновление исходного экземпляра до последней версии Discourse, установка Discourse на новый хост, заморозка исходного экземпляра, создание резервной копии Discourse на исходном хосте, синхронизация изображений и других данных между хостами через rsync, восстановление резервной копии на новом хосте.

Что касается ошибок дублирования индекса, я думаю, что если вы сможете выполнить restart web_only и решить их через интерфейс, это будет гораздо проще, чем делать это в базе данных. Хотя я не помню, чтобы видел что-то подобное с theme_field_id раньше.

Строка непосредственно над rake aborted! в логах упоминает, что discourse-voting теперь называется discourse-topic-voting. Попробуйте обновить ссылку в разделе плагинов на актуальную и посмотрите, поможет ли это?

Можете уточнить, какой ресурс имеет дублирующийся идентификатор? Я точно не понял, что означает theme_field_id.

Кстати, я не останавливал контейнер web_only. Обычно я сначала инициализирую контейнер, чтобы минимизировать время простоя:

Попробую. Подозреваю, что это не поможет, так как, думаю, GitHub молча перенаправляет запросы на своей стороне, а предупреждение возникает только на стороне Discourse. :slight_smile:

Если у вас есть работающий сайт с установленным Data Explorer, я думаю, вы сможете увидеть, на что это может указывать, используя следующий запрос:

SELECT *
FROM theme_fields
WHERE id = 3

Я не знал об этом плагине — это здорово! У меня он не установлен, но я могу получить доступ к контейнеру данных и выполнять запросы.

Отличная подсказка! Если я правильно читаю сообщение об ошибке, проблема не в таблице theme_fields, так как у этой конкретной таблицы нет поля theme_field_id. Я не проверял исходный код, но рискну предположить, что первый аргумент функции add_index() — это имя таблицы, а второй — имя столбца.

Исходя из этого, похоже, что речь идет о таблице javascript_caches.

== 20230817174049 EnsureJavascriptCacheIsUniquePerTheme: migrating ============
-- remove_index(:javascript_caches, :theme_id)
   -> 0.0208s
-- add_index(:javascript_caches, :theme_id, {:unique=>true})
   -> 0.0079s
-- remove_index(:javascript_caches, :theme_field_id)
   -> 0.0026s
-- add_index(:javascript_caches, :theme_field_id, {:unique=>true})

Поэтому я проверил структуру этой таблицы, и в ней действительно есть столбец theme_field_id:

discourse=# \d javascript_caches
                                          Table "public.javascript_caches"
     Column     |            Type             | Collation | Nullable |                    Default
----------------+-----------------------------+-----------+----------+-----------------------------------------------
 id             | bigint                      |           | not null | nextval('javascript_caches_id_seq'::regclass)
 theme_field_id | bigint                      |           |          |
 digest         | character varying           |           |          |
 content        | text                        |           | not null |
 created_at     | timestamp without time zone |           | not null |
 updated_at     | timestamp without time zone |           | not null |
 theme_id       | bigint                      |           |          |
 source_map     | text                        |           |          |
Indexes:
    "javascript_caches_pkey" PRIMARY KEY, btree (id)
    "index_javascript_caches_on_digest" btree (digest)
    "index_javascript_caches_on_theme_field_id" btree (theme_field_id)
    "index_javascript_caches_on_theme_id" btree (theme_id)
Check constraints:
    "enforce_theme_or_theme_field" CHECK (theme_id IS NOT NULL AND theme_field_id IS NULL OR theme_id IS NULL AND theme_field_id IS NOT NULL)
Foreign-key constraints:
    "fk_rails_58f94aecc4" FOREIGN KEY (theme_id) REFERENCES themes(id) ON DELETE CASCADE
    "fk_rails_ed33506dbd" FOREIGN KEY (theme_field_id) REFERENCES theme_fields(id) ON DELETE CASCADE

Я смог выполнить запрос к этой таблице (поля content были сокращены, чтобы я мог их прочитать) и вижу дубликаты. Но я не уверен, к каким последствиям это может привести.

discourse=# select id, theme_field_id, digest, substring(content from 1 for 64) as content_64, created_at, updated_at, theme_id, substring(source_map from 1 for 64) as source_64 from javascript_caches where theme_field_id = 3;
 id | theme_field_id |                  digest                  |                            content_64                            |         created_at         |         updated_at         | theme_id |                            source_64
----+----------------+------------------------------------------+------------------------------------------------------------------+----------------------------+----------------------------+----------+------------------------------------------------------------------
  1 |              3 | d0b6ec642d5649064ff0501cadc775a9217b16e0 | "define"in window&&define("discourse/theme-3/initializers/theme- | 2019-02-25 01:26:56.606537 | 2023-08-18 20:47:19.596923 |          | {"version":3,"sources":["discourse/initializers/theme-field-3-co
  2 |              3 | 7fd74ecf4448afccdbcd9ccde87acddb4ec6f514 | "define"in window&&define("discourse/theme-3/initializers/theme- | 2019-02-25 01:26:58.228209 | 2023-08-18 20:50:41.049209 |          | {"version":3,"sources":["discourse/initializers/theme-field-3-co

Содержимое показалось мне знакомым, поэтому я зашел в Настройки → Тема → Моя тема → Общие → Заголовок, внес незначительное изменение и сохранил. Я вижу, что одна из записей обновилась, а другая осталась старой…

discourse=# select id, theme_field_id, digest, substring(content from 1 for 64) as content_64, created_at, updated_at, theme_id, substring(source_map from 1 for 64) as source_64 from javascript_caches where theme_field_id = 3;
 id | theme_field_id |                  digest                  |                            content_64                            |         created_at         |         updated_at         | theme_id |                            source_64
----+----------------+------------------------------------------+------------------------------------------------------------------+----------------------------+----------------------------+----------+------------------------------------------------------------------
  2 |              3 | 7fd74ecf4448afccdbcd9ccde87acddb4ec6f514 | "define"in window&&define("discourse/theme-3/initializers/theme- | 2019-02-25 01:26:58.228209 | 2023-08-18 20:50:41.049209 |          | {"version":3,"sources":["discourse/initializers/theme-field-3-co
  1 |              3 | 7f4132b1f9ced1b90b8f8fc24812cc11e81fea8d | "define"in window&&define("discourse/theme-3/initializers/theme- | 2019-02-25 01:26:56.606537 | 2023-09-13 21:56:56.312263 |          | {"version":3,"sources":["discourse/initializers/theme-field-3-co
(2 rows)

Снова не уверен, к каким последствиям это может привести, безопасно ли удалять «старую» запись или есть какой-то другой способ «перестроить» кэши, чтобы это исправить?

Честно говоря, у меня пока 0 из 2 попыток, так что, вероятно, я не лучший источник информации по этому вопросу. :slight_smile:

Есть несколько предыдущих тем, где происходило что-то похожее с index_tags_on_name, если они могут пригодиться? Например:

Пересоберите ваш контейнер данных. Плагин AI требует расширения, которого у вас пока нет.

Очень ценю вашу помощь! Похоже, вы направили меня по верному пути. :star_struck:

Я сделал резервную копию таблицы с помощью pg_dump, удалил старую дублирующую запись, и загрузка прошла успешно. :+1:

Спасибо за подтверждение! Я предполагал, что мне, возможно, потребуется обновить контейнер данных (или иначе установить pgvector). Я откладывал это, так как не хотел сталкиваться с простоями.

Судя по обсуждению в теме discourse-ai, PG15 уже на горизонте, поэтому, возможно, я просто немного подожду.

Похоже, зависимость от pgvector нужна для функции Embeddings, которую я не планировал использовать, но, к сожалению, похоже, что всё это упаковано вместе. Мне в основном хотелось поэкспериментировать с некоторыми функциями магии переписывания от OpenAI / ChatGPT, так что, возможно, это ещё одна причина подождать следующего крупного обновления контейнера данных. :slight_smile:

Вы можете подождать, если хотите, но пользователи с одним контейнером (а это подавляющее большинство тех, кто размещает сервисы самостоятельно) обновляют PostgreSQL, возможно, каждый раз при обновлении системы, поэтому нет особых причин ждать, кроме нескольких лишних минут простоя. После восстановления базы данных вам потребуется пересобрать (или, возможно, просто уничтожить и запустить заново) контейнер web_only.

Это основная причина. Даже если это всего несколько минут, я бы предпочёл минимизировать простой, если это не критично.

Кстати, возможно, мне стоит воздержаться от использования бета-плагина только из-за риска простоя. :stuck_out_tongue:

Я в общих чертах следил за обсуждениями обновлений с «нулевым простоем». Возможно, это просто розовые очки, когда оглядываешься на недавнее прошлое, но мне кажется, что за последний год я мог использовать /admin/upgrade для большинства обновлений, поэтому сейчас я сосредоточен на более важных проектах и утратил интерес к подходу с нулевым простоем.

Когда в январе я переехал на более мощный хост, это было «с нулевым простоем» в том смысле, что пользователи могли продолжать получать доступ к контенту на сайте, но был краткий период, когда всё работало только в режиме чтения во время перехода. Думаю, я мог бы использовать такой подход для крупного обновления контейнера data, если бы действительно хотел минимизировать простой во время следующего большого обновления data.

P.S. За годы я прочитал тонну ваших постов в этом сообществе. Спасибо за всю вашу работу по поддержке сообщества! :star2:

Действительно! Я уже давно не выполнял обновление для своих клиентов с настройкой «пересборка по мере необходимости». Для сайтов, подобных вашему, моя панель управления выполнит пересборку и пост-апгрейдные миграции после запуска нового контейнера (если в вашем существующем контейнере эти миграции настроены так, чтобы не выполняться в первый раз).