업그레이드 오류 - rake db:migrate theme_field_id 인덱스

최신 릴리스 노트(3.2.0.beta1)에서 이전에 본 적 없는 discourse-ai 플러그인을 발견했습니다. 그래서 이 플러그인을 추가하고 동시에 제 discourse 인스턴스를 업그레이드해 보았습니다.

제목에 언급했듯이, 현재 부트스트랩 과정에서 rake db:migrate가 theme_field_id에 유니크 인덱스를 생성하지 못해 오류가 발생하고 있습니다. 제가 이 지점까지 어떻게 도달했는지에 대한 세부 사항은 다음과 같습니다…

초기 업그레이드 시도 (patch-package 오류)

분리된 컨테이너 설치(split container install)를 사용 중이므로:

  • 새 discourse-ai 플러그인을 추가하기 위해 web_only.yml 파일을 수정했습니다.

    예: plugins 훅에 한 줄 추가
    ## Plugins go here
    ## see https://meta.discourse.org/t/19157 for details
    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 → 부트스트랩 (pg-vector 오류)

다시 시도하기 전에 최신 런처 업데이트가 있는지 확인하는 것이 해롭지 않을 것 같아서:

  • git pull을 실행하여 최신 런처 관련 업데이트를 받았습니다.
  • ./launcher bootstrap web_only를 다시 실행했습니다.

이번에는 pg-vector와 관련된 오류 메시지가 나타났습니다.

:page_facing_up: discourse-ai 사용 상태의 부트스트랩 로그 스니펫

discourse-ai 플러그인을 다시 검토할 때를 위해 PostgreSQL 버전을 기록해 두었습니다.

  • web_only:
    • 클라이언트: psql (PostgreSQL) 13.10 (Debian 13.10-1.pgdg110+1)
  • data:
    • 서버: PostgreSQL 13.9 (Debian 13.9-1.pgdg110+1)

discourse-ai 플러그니 제거 → 부트스트랩

그리고 web_only.yml 파일에서 discourse-ai 플러그인을 제거하고 다시 부트스트랩을 실행했습니다.

놀랍게도 여전히 오류가 발생했지만, 이번에는 rake db:migrate가 index_javascript_caches_on_theme_field_id 유니크 인덱스를 생성하지 못하는 것으로 보였습니다. 상세 내용은: Key (theme_field_id)=(3) is duplicated. (키(theme_field_id)=(3)가 중복됨)

:page_facing_up: discourse-ai 미사용 상태의 부트스트랩 로그 스니펫

도움을 요청합니다 :folded_hands:

그리고 이제 도움을 구하기 위해 이곳에 왔습니다. 다른 분들도 이런 현상을 본 적이 있을 수 있으므로, 더 깊이 파고들기 전에 커뮤니티의 통찰력을 얻고 싶었습니다.

참고로, 현재 3.2.0.beta1-dev (993ed10cf0 ~ 8월 9일) 버전이 설치되어 있습니다.

그리고 이것이 이 문제와 관련이 있다고 생각하지는 않지만, 올해 초에 호스트 머신 간 이동을 했다는 언급도 해두겠습니다… 그 이후로 관리자 UI를 통해 여러 번 Discourse 업데이트를 수행했습니다.

마이그레이션 방식

기억이 정확하지는 않지만, 기본적으로 소스 인스턴스를 최신 Discourse 버전으로 업그레이드하고, 새 호스트에 discourse를 설치한 후, 소스를 프리즈(동결)하고, 소스에서 Discourse 백업을 수행하고, 호스트 간에 rsync로 이미지 등을 동기화하고, 새 호스트에서 백업을 복원하는 과정이었습니다.

중복된 인덱스 오류에 대해서는, restart web_only를 실행한 뒤 UI에서 처리하는 것이 데이터베이스에서 직접 처리하는 것보다 훨씬 간단합니다. 다만 theme_field_id에 대해서는 이전에 본 적이 없습니다.

로그에서 rake aborted! 바로 위 줄에 discourse-voting이 이제 discourse-topic-voting으로 변경되었다는 내용이 언급되어 있습니다. 플러그인 섹션의 링크를 최신 버전으로 업데이트해 보고 도움이 되는지 확인해 보시는 것은 어떨까요?

어떤 리소스에 대해 중복 ID가 발생했는지 명확히 설명해 주실 수 있을까요? theme_field_id가 정확히 무엇을 가리키는지 확신이 서지 않았습니다.

참고로, 저는 web_only 컨테이너를 종료하지 않았습니다. 보통 중단 시간을 최소화하기 위해 먼저 컨테이너를 부팅합니다:

–

한번 시도해 보겠습니다. GitHub가 백그라운드에서 조용히 리다이렉트를 처리하고 있을 것이며, 이는 단지 Discourse 측의 경고일 뿐이라고 생각하기 때문에 도움이 되지 않을 것 같습니다. :slight_smile:

데이터 탐색기가 설치된 실행 중인 웹사이트가 있다면, 다음 쿼리를 사용하여 그것이 무엇을 가리키는지 확인할 수 있을 것입니다:

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

해당 테이블을 쿼리할 수 있었습니다(읽을 수 있도록 콘텐츠 필드를 줄여서) 그리고 중복된 항목들을 볼 수 있습니다. 하지만 이 중복 항목들의 영향이 무엇인지 확실하지 않습니다.

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

콘텐츠가 익숙해 보여서 Customize → Theme → My Theme → Common → Head로 들어가서 작은 수정을 하고 저장했는데, 항목 중 하나가 업데이트되고 다른 하나는 여전히 오래된 상태로 남아있는 것을 볼 수 있습니다…

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의 마법 같은 재작성(rewrite) 기능들을 직접 써 보고 싶었기 때문에, 다음 대규모 데이터 컨테이너 업데이트를 기다리는 것이 또 하나의 이유가 될 수 있겠습니다. :slight_smile:

원한다면 기다릴 수 있지만, 단일 컨테이너를 사용하는 사용자들(셀프 호스터의 대다수)은 업그레이드를 할 때마다 포스트그레SQL을 업그레이드하는 경우가 많습니다. 따라서 몇 분간의 추가 다운타임 외에는 기다릴 이유가 크게 없습니다. 데이터베이스를 다시 빌드한 후에는 web_only 컨테이너도 다시 빌드(또는 단순히 삭제 후 재시작)해야 합니다.

그것이 가장 큰 이유입니다. 비록 몇 분에 불과하더라도, 비긴급 사항이라면 최소화하는 것이 좋습니다.

이와 관련하여, 잠재적인 다운타임 가능성만으로도 베타 플러그인 사용을 보류해야 할 것 같습니다. :stuck_out_tongue:

‘제로 다운타임’ 업그레이드에 대한 몇몇 토론을 간접적으로 지켜봤습니다. 아마도 최근의 역사를 회고하며 장미빛 안경을 쓰고 보는 것일 수 있지만, 지난 1년 동안 대부분의 업데이트에서 /admin/upgrade를 사용할 수 있었기 때문에, 당분간 더 중요한 프로젝트에 집중하다가 제로 다운타임 방식에 대한 관심을 잃었습니다.

1월에 더 큰 호스팅으로 이주했을 때, 사용자가 사이트 콘텐츠에 계속 접근할 수 있었기 때문에 '제로 다운타임’이라 할 수 있었지만, 전환 과정에서 잠깐 읽기 전용 모드로 전환되는 기간이 있었습니다. 만약 다음 큰 data 업그레이드 시 다운타임을 정말로 최소화하고 싶다면, 주요 data 컨테이너 업그레이드에 이 방식을 사용할 수 있을 것입니다.

P.S. 지난 몇 년간 이곳 커뮤니티에서 작성하신 게시글을 정말 많이 읽었습니다. 커뮤니티를 지원해 주신 모든 노력에 감사드립니다! :star2:

실제로 그렇습니다. "필요할 때 재빌드"하는 클라이언트 사이트의 업그레이드를 꽤 오랫동안 하지 않았습니다. 귀하의 사이트와 같은 경우, 새 컨테이너가 시작되면 대시보드가 재빌드를 수행하고 업그레이드 후 마이그레이션을 실행합니다(기존 컨테이너에 해당 마이그레이션이 첫 실행 시 실행되지 않도록 설정되어 있는 경우).