マイグレーションエラー:「ActiveRecord::NotNullViolation: PG::NotNullViolation: ERROR: column "private_message" contains null values」

サイトのアップグレードを行っていますが、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]

申し訳ありませんが、すべてのクエリを同時に実行する必要があります。それ以外の場合、新規投稿が作成されるとカウントがずれてしまいます。

そう言われると確かにその通りですね……それに、クエリを 1 つ実行するだけでも思ったより手間がかかりました!

[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 当社のプロジェクトマネージャーによると、これは解決済みであることを確認させてください。

申し訳ありません、Alan。はい、これは解決しました。お手伝いいただきありがとうございます!

同じ問題に直面している他の方のために、単純に ./launcher rebuild app を実行すれば、データベースの移行を行うブートストラップ処理の間、サイトが長時間ダウンする可能性があることを除いて、問題なく動作するはずです。これは安全で簡単なオプションであり、2 コンテナインストールでない限り、皆さんが行うことになる方法です。

私はブートストラップ全体でサイトをダウンさせたくなかったので、この大規模なフォーラム(投稿数 500 万件、1 日あたりのページビュー数約 5 万件?)では、Alan の助けを借りて見つけた私の解決策は、アップグレード後の移行をオフにしてブートストラップを実行し(移行にはほとんど時間がかかりません)、新しいコンテナを起動してからアップグレード後の移行を実行するというものでした(移行には 20〜40 分かかりましたが、時計をあまり見ていませんでした)。

もし興味のある方がいれば、その後 docker_manager を使ってアップグレードを行うと、はるかにスムーズに行えることを学びました。したがって、大規模なフォーラムを持っていてブートストラップに問題がある場合は、これを推奨します。