/polls/voters.json 엔드포인트를 호출할 때 페이지네이션을 사용하는 경우, 첫 번째 호출에서는 예상대로 25명의 사용자가 반환되지만, 두 번째 페이지에서는 26명의 사용자가 반환됩니다. 이 중 1명은 첫 번째 호출에서도 이미 반환된 사용자입니다. 사용자가 25명을 초과하는 경우 이 문제는 항상 재현됩니다.
문제는 discourse의 poll 플러그인 여기에서 발생합니다:
PostgreSQL의 BETWEEN 구문이 경계값을 포함(inclusive)한다는 점을 고려하지 않고, 경계값을 제외(exclusive)한다고 가정하여 오프셋이 계산되고 있습니다.
첫 번째 쿼리가 실제로 25개를 반환하는 이유는 계산된 행이 0이 아닌 1부터 시작하기 때문입니다.
WHERE pv.poll_id IN (:poll_ids)
/* where */
) v
ORDER BY digest, CASE WHEN rank = 'Abstain' THEN 1 ELSE CAST(rank AS integer) END, username
LIMIT :limit OFFSET :offset
SQL
이 PR에서는 먼저 문제를 재현하는 테스트를 추가한 뒤, Rob의 간단한 수정 사항을 적용하여 테스트가 통과하도록 했습니다.
더 우아한 해결책(제가 선호했던 방식)도 시도해 보았는데, 중복 투표자를 방지하는 것은 확실하지만, 어떤 투표자가 어느 페이지에 반환되는지(첫 페이지를 포함하여) 변경됩니다. 이는 프론트엔드에서 이를 어떻게 처리하느냐에 따라 깨지는 변경(breaking change)으로 간주될 수 있습니다(아직 확인은 하지 않았습니다).
한 발짝 물러서서 생각해 보면, 이 엔드포인트를 호출할 때 limit 매개변수의 진정한 의미가 무엇인지 궁금해집니다. limit은 반환되는 전체 투표자 수를 제한하는 것이 아니라, 각 투표 옵션별로 반환되는 투표자 수만을 제한합니다. 제가 멀티 초이스(multi-choice) 투표에 대해 추가한 테스트에서 이 효과를 확인할 수 있습니다 여기 - 첫 페이지는 옵션당 2명의 투표자로 제한되어 있지만, 실제로는 총 3명의 서로 다른 투표자가 반환됩니다(옵션에 분산되어). 우아한 해결책(즉, LIMIT :limit OFFSET :offset 사용)으로 전환하면 limit은 투표자가 아닌 *투표(votes)*의 총 수에 적용됩니다. 이것이 더 나은지 더 직관적인지는 100% 확신하지 못합니다.
어쨌든, 저는 이 프로젝트에 새로 온 것이므로 너무 깊이 생각하고 있을 수 있습니다. 간단한 해결책은 중복 투표자를 제거하고 큰 혼란을 일으키지 않으므로, 이 방향으로 진행하는 것이 좋을 수도 있습니다. 부모 저장소에 PR을 제출하기 전에 피드백을 기다리겠습니다.
–
부연하자면, 이 코드 부분에 또 다른 버그가 있는 것 같습니다. 투표자를 로드하는 쿼리는 digest, rank, username 순서로 정렬되지만, rank로 정렬할 때 다음과 같은 조건을 사용합니다:
CASE WHEN rank = 'Abstain' THEN 1 ELSE CAST(rank AS integer) END
그러나 ’Abstain’은 실제로 rank 1이 아니라 rank 0에 해당합니다. rank 1은 ’1’로 반환될 수도 있습니다. 이로 인해 쿼리 간 정렬이 비결정적(non-deterministic)이 될 수 있으며, 이는 투표자 수와 사용되는 limit 값에 따라 페이지네이션 쿼리 시 실제로 투표자를 놓칠 수 있음을 의미합니다. 제 새로운 테스트에서는 비결정적인 특성을 우회하기 위해 반환된 투표자를 정렬해야 했습니다. (비결정적이므로 테스트에서 재현하기가 쉽지 않겠지만, 시도해 볼 수 있습니다…)