마이그레이션 오류: `ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: "private_message" 열에 null 값이 포함되어 있습니다`

사이트 업그레이드를 진행하던 중 PG::NotNullViolation: ERROR: column "private_message" contains null values 오류로 실패하고 있습니다. private_message 컬럼이 어디에 있는지 찾을 수 없습니다. 비공식 플러그인은 설치되어 있지 않습니다.

아, 잠깐. 이걸 발견했습니다:

(See full trace by running task with --trace)                                                                                            
I, [2020-08-18T18:19:13.253667 #1]  INFO -- : == 20200818084329 UpdatePrivateMessageOnPostSearchData: migrating ============= 
-- execute("DELETE FROM post_search_data\nWHERE post_id IN (\n  SELECT posts.id\n  FROM posts\n  LEFT JOIN topics ON topics.id = posts.topic_id\n  WHERE topics.id IS NULL\n)\n")
   -> 21.9072s                                                                                                       
-- execute("DELETE FROM post_search_data\nWHERE post_id IN (\n  SELECT post_search_data.post_id\n  FROM post_search_data\n  LEFT JOIN posts ON posts.id = post_search_data.post_id\n  WHERE posts.id IS NULL\n)\n")                                                                       
   -> 47.2663s
-- execute("UPDATE post_search_data\nSET private_message = true\nFROM posts\nINNER JOIN topics ON topics.id = posts.topic_id AND topics.archetype = 'private_message'\nWHERE posts.id = post_search_data.post_id\n")                                                                      
   -> 107.2137s             
-- execute("UPDATE post_search_data\nSET private_message = false\nFROM posts\nINNER JOIN topics ON topics.id = posts.topic_id AND topics.archetype <> 'private_message'\nWHERE posts.id = post_search_data.post_id\n")
   -> 834.3738s                                                                                                                              
-- change_column_null(:post_search_data, :private_message, false)

@tgxworld 님, 이건 아마 다음 커밋 때문인 것 같습니다:

음, 이건 좀 이상하네요… Rails 콘솔에서 아래 쿼리를 실행해 주시고 결과를 알려 주시겠어요?

DB.query_single(<<~SQL)
SELECT COUNT(*) FROM post_search_data
SQL

DB.query_single(<<~SQL)
SELECT COUNT(*)
FROM post_search_data
LEFT JOIN posts ON posts.id = post_search_data.post_id
LEFT JOIN topics ON topics.id = posts.topic_id 
WHERE topics.id IS NULL
SQL

DB.query_single(<<~SQL)
SELECT COUNT(*)
FROM post_search_data
LEFT JOIN posts ON posts.id = post_search_data.post_id
WHERE posts.id IS NULL
SQL

DB.query_single(<<~SQL)
SELECT COUNT(*)
FROM post_search_data
INNER JOIN posts ON posts.id = post_search_data.post_id
INNER JOIN topics ON topics.id = posts.topic_id
SQL
[2] pry(main)> 
[3] pry(main)> DB.query_single(<<~SQL)
[3] pry(main)* SELECT COUNT(*) FROM post_search_data
[3] pry(main)* SQL
=> [2200178]
[4] pry(main)> 
[5] pry(main)> DB.query_single(<<~SQL)
[5] pry(main)* SELECT COUNT(*)
[5] pry(main)* FROM post_search_data
[5] pry(main)* LEFT JOIN posts ON posts.id = post_search_data.post_id
[5] pry(main)* LEFT JOIN topics ON topics.id = posts.topic_id 
[5] pry(main)* WHERE topics.id IS NULL
[5] pry(main)* SQL

=> [39]
[6] pry(main)> 
[7] pry(main)> DB.query_single(<<~SQL)
[7] pry(main)* SELECT COUNT(*)
[7] pry(main)* FROM post_search_data
[7] pry(main)* LEFT JOIN posts ON posts.id = post_search_data.post_id
[7] pry(main)* WHERE posts.id IS NULL
[7] pry(main)* SQL
=> [0]
[8] pry(main)> 
[9] pry(main)> DB.query_single(<<~SQL)
[9] pry(main)* SELECT COUNT(*)
[9] pry(main)* FROM post_search_data
[9] pry(main)* INNER JOIN posts ON posts.id = post_search_data.post_id
[9] pry(main)* INNER JOIN topics ON topics.id = posts.topic_id
[9] pry(main)* SQL

마지막 카운트가 실행되지 않은 것 같습니다.

죄송합니다. 클릭하여 복사할 때 터미널의 줄바꿈 문자가 제대로 복사되지 않아 계속 문제가 발생하고 있습니다.

[2] pry(main)> 
[3] pry(main)> DB.query_single(<<~SQL)
[3] pry(main)* SELECT COUNT(*)
[3] pry(main)* FROM post_search_data
[3] pry(main)* INNER JOIN posts ON posts.id = post_search_data.post_id
[3] pry(main)* INNER JOIN topics ON topics.id = posts.topic_id
[3] pry(main)* SQL
=> [2200797]

모든 쿼리를 함께 실행해야 해서 죄송합니다 :slight_smile: 그렇지 않으면 새로 생성되는 게시글로 인해 카운트가 틀어질 수 있습니다.

그렇게 말씀하시니 너무 당연해 보이네요. . . 그런데 쿼리 하나만 실행하는 데 더 많은 노력이 들었습니다!

[1] pry(main)> DB.query_single(<<~SQL)
[1] pry(main)* SELECT COUNT(*) FROM post_search_data
[1] pry(main)* SQL
=> [2200995]
[2] pry(main)> 
[3] pry(main)> DB.query_single(<<~SQL)
[3] pry(main)* SELECT COUNT(*)
[3] pry(main)* FROM post_search_data
[3] pry(main)* LEFT JOIN posts ON posts.id = post_search_data.post_id
[3] pry(main)* LEFT JOIN topics ON topics.id = posts.topic_id 
[3] pry(main)* WHERE topics.id IS NULL
[3] pry(main)* SQL
=> [39]
[4] pry(main)> 
[5] pry(main)> DB.query_single(<<~SQL)
[5] pry(main)* SELECT COUNT(*)
[5] pry(main)* FROM post_search_data
[5] pry(main)* LEFT JOIN posts ON posts.id = post_search_data.post_id
[5] pry(main)* WHERE posts.id IS NULL
[5] pry(main)* SQL
=> [0]
[6] pry(main)> 
[7] pry(main)> DB.query_single(<<~SQL)
[7] pry(main)* SELECT COUNT(*)
[7] pry(main)* FROM post_search_data
[7] pry(main)* INNER JOIN posts ON posts.id = post_search_data.post_id
[7] pry(main)* INNER JOIN topics ON topics.id = posts.topic_id
[7] pry(main)* SQL
=> [2200956]
[8] pry(main)> 

@pfaffman 우리 PM의 확인에 따라 이 문제가 해결되었는지 확인하고 싶어서요.

앨런, 죄송합니다. 네, 이 문제는 해결되었습니다. 도움을 주셔서 감사합니다!

다른 분들 중 이 문제를 겪고 계신 경우, ./launcher rebuild app을 그대로 실행하면 부트스트랩 단계에서 데이터베이스 마이그레이션이 진행되면서 사이트가 상당한 시간 동안 다운될 수 있다는 점을 제외하고는 문제가 없을 것입니다. 이는 안전하고 간단한 방법이며, 2컨테이너 설치가 아니라면 아무튼 그렇게 할 수밖에 없습니다.

저는 부트스트랩 전체 기간 동안 사이트를 다운시키고 싶지 않았습니다. 이 대형 포럼(500만 개의 게시물, 하루 약 5만 페이지뷰?)의 경우, 제 해결책(앨런의 도움 덕분에 겨우 알아낸 것)은 업그레이드 후 마이그레이션을 꺼둔 채로 부트스트랩을 실행하는 것이었습니다(마이그레이션에는 거의 시간이 걸리지 않았습니다). 그런 다음 새 컨테이너를 실행하고 업그레이드 후 마이그레이션을 수행했습니다(마이그레이션에는 20~40분이 걸렸는데, 시계를 제대로 보고 있지는 않았습니다).

혹시 관심 있는 분을 위해 말씀드리자면, 이후에 docker_manager를 사용하여 업그레이드를 수행하면 훨씬 더 부드럽게 진행될 수 있음을 알게 되었습니다. 따라서 다른 분들 중 대형 포럼을 운영하면서 부트스트랩에 문제가 있는 경우 이 방법을 권장합니다.