데이터베이스 문제 해결 및 디버깅

이 섹션은 머리에서 쿵 소리가 나는 데이터베이스 문제를 만났을 때 참고할 수 있는 복사-붙여넣기 내용을 제공하는 데 도움을 줍니다.

첫 번째 단계는 Slack에서 오류를 검색하거나 GitLab <내 오류>로 Google에서 검색하는 것입니다.

사용 가능한 RAILS_ENV:

  • production (일반적으로 기본 GDK 데이터베이스에는 사용하지 않지만, Omnibus와 같은 다른 설치에는 필요할 수 있습니다).
  • development (이것이 귀하의 주요 GDK 데이터베이스입니다).
  • test (RSpec과 같은 테스트에 사용됩니다).

모든 내용을 삭제하고 처음부터 시작하기

모든 내용을 삭제하고 빈 데이터베이스로 처음부터 시작하려면 (약 1분 소요):

bundle exec rake db:reset RAILS_ENV=development

빈 데이터베이스에 샘플 데이터로 초기화하려면 (약 4분 소요):

bundle exec rake dev:setup

모든 내용을 삭제하고 샘플 데이터로 처음부터 시작하려면 (약 4분 소요). 이 명령은 db:reset도 실행하고 데이터베이스 전용 마이그레이션도 수행합니다:

bundle exec rake db:setup RAILS_ENV=development

테스트 데이터베이스에 문제가 발생하는 경우, 중요한 데이터가 없기 때문에 모든 내용을 삭제하는 것이 안전합니다:

bundle exec rake db:reset RAILS_ENV=test

마이그레이션 조정

  • bundle exec rake db:migrate RAILS_ENV=development: MR에서 가져온 대기 중인 마이그레이션을 실행합니다.
  • bundle exec rake db:migrate:status RAILS_ENV=development: 모든 마이그레이션이 up인지 down인지 확인합니다.
  • bundle exec rake db:migrate:down:main VERSION=20170926203418 RAILS_ENV=development: 마이그레이션을 제거합니다.
  • bundle exec rake db:migrate:up:main VERSION=20170926203418 RAILS_ENV=development: 마이그레이션을 설정합니다.
  • bundle exec rake db:migrate:redo:main VERSION=20170926203418 RAILS_ENV=development: 특정 마이그레이션을 다시 실행합니다.

위 명령에서 mainci 데이터베이스에 대해 실행하려면 바꿉니다.

데이터베이스에 수동으로 접근하기

다음 명령 중 하나로 데이터베이스에 접근합니다. 이 모든 명령은 동일한 위치로 이동합니다.

gdk psql -d gitlabhq_development
bundle exec rails dbconsole -e development
bundle exec rails db -e development
  • \q: 종료/나가기
  • \dt: 모든 테이블 목록
  • \d+ issues: issues 테이블의 열 목록
  • CREATE TABLE board_labels();: board_labels라는 테이블 생성
  • SELECT * FROM schema_migrations WHERE version = '20170926203418';: 마이그레이션이 실행되었는지 확인
  • DELETE FROM schema_migrations WHERE version = '20170926203418';: 마이그레이션을 수동으로 제거

GUI로 데이터베이스 접근하기

대부분의 GUI(DataGrip, RubyMine, DBeaver)는 데이터베이스에 대한 TCP 연결을 요구하지만, 기본적으로 데이터베이스는 UNIX 소켓에서 실행됩니다. 이러한 도구에서 데이터베이스에 접근하기 위해서는 몇 가지 단계가 필요합니다:

  1. GDK 루트 디렉터리에서 다음을 실행합니다:

    gdk config set postgresql.host localhost
    
  2. gdk.yml을 열고 다음 줄이 포함되어 있는지 확인합니다:

    postgresql:
       host: localhost
    
  3. GDK를 재구성합니다:

    gdk reconfigure
    
  4. 데이터베이스 GUI에서 호스트로 localhost, 포트로 5432, 데이터베이스로 gitlabhq_development을 선택합니다. 또한 연결 문자열 postgresql://localhost:5432/gitlabhq_development를 사용할 수 있습니다.

이제 새로운 연결이 작동해야 합니다.

Visual Studio Code로 GDK 데이터베이스에 접근하기

Visual Studio Code에서 PostgreSQL 확장을 사용하여 데이터베이스 연결을 생성하여 GDK 데이터베이스에 접근하고 탐색합니다.

전제 조건:

데이터베이스 연결을 생성하려면:

  1. 활동 바에서 PostgreSQL Explorer 아이콘을 선택합니다.
  2. 열린 창에서 +를 선택하여 새 데이터베이스 연결을 추가합니다:
  3. 데이터베이스의 hostname을 입력합니다. GDK 디렉터리의 PostgreSQL 폴더 경로를 사용하세요.
    • 예시: /dev/gitlab-development-kit/postgresql
  4. 인증할 PostgreSQL 사용자를 입력합니다. PostgreSQL 설치 시 다르게 지정되지 않았다면 로컬 사용자 이름을 사용하세요.

    PostgreSQL 사용자 이름을 확인하려면:

    1. gitlab 디렉터리에 있는지 확인합니다.
    2. PostgreSQL 데이터베이스에 접근합니다. rails db를 실행합니다. 출력은 다음과 같아야 합니다:

         psql (14.9)
         도움말은 "help"를 입력하세요.
      
         gitlabhq_development=#
      
    3. 반환된 PostgreSQL 프롬프트에서 \conninfo를 실행하여 연결된 사용자와 연결을 설정하는 데 사용된 포트를 표시합니다. 예를 들어:

         데이터베이스 "gitlabhq_development"에 사용자 "root"로 호스트 "localhost" (주소 "127.0.0.1")에서 포트 "5432"로 연결되어 있습니다.
      
  5. PostgreSQL 사용자 비밀번호를 입력하라는 메시지가 나오면 설정한 비밀번호를 입력하거나 필드를 비워둡니다.
    • Postgres 서버가 실행 중인 동일한 머신에 로그인 중이므로 비밀번호는 필요하지 않습니다.
  6. 연결할 포트 번호를 입력합니다. 기본 포트 번호는 5432입니다.
  7. SSL 연결을 사용할까요? 필드에서 설치에 적합한 연결을 선택합니다. 옵션은 다음과 같습니다:
    • 안전한 연결 사용
    • 표준 연결 (기본값)
  8. 선택적 연결할 데이터베이스 필드에 gitlabhq_development를 입력합니다.
  9. 데이터베이스 연결에 대한 표시 이름 필드에 gitlabhq_development를 입력합니다.

이제 gitlabhq_development 데이터베이스 연결이 PostgreSQL Explorer 창에 표시됩니다.

화살표를 사용하여 GDK 데이터베이스의 내용을 확장하고 탐색합니다.

연결할 수 없는 경우, 먼저 GDK가 실행 중인지 확인하고 다시 시도하십시오. VS Code의 PostgreSQL Explorer 확장을 사용하는 방법에 대한 추가 지침은 사용 섹션에서 확인하십시오.

FAQ

ActiveRecord::PendingMigrationError with Spring

Spring pre-loader와 함께 사양을 실행할 때 테스트 데이터베이스가 손상된 상태가 될 수 있습니다. 마이그레이션을 실행하거나 테스트 데이터베이스를 삭제/재설정해도 효과가 없습니다.

$ bundle exec spring rspec some_spec.rb
...
실패/오류: ActiveRecord::Migration.maintain_test_schema!

ActiveRecord::PendingMigrationError:


  마이그레이션이 보류 중입니다. 이 문제를 해결하려면 다음을 실행하세요:

    bin/rake db:migrate RAILS_ENV=test
# ~/.rvm/gems/ruby-2.3.3/gems/activerecord-4.2.10/lib/active_record/migration.rb:392:in `check_pending!'
...
0 예제, 0 실패, 1 예외가 발생했습니다.

해결하려면 스펙 실행 간에 살아있는 스프링 서버와 앱을 종료할 수 있습니다.

$ ps aux | grep spring
eric             87304   1.3  2.9  3080836 482596   ??  Ss   10:12AM   4:08.36 spring app    | gitlab | started 6 hours ago | test mode
eric             37709   0.0  0.0  2518640   7524 s006  S    Wed11AM   0:00.79 spring server | gitlab | started 29 hours ago
$ kill 87304
$ kill 37709

db:migrate database version is too old to be migrated 오류

사용자는 db:migrate가 현재 스키마 버전이 Gitlab::Database 라이브러리 모듈에서 정의된 MIN_SCHEMA_VERSION보다 오래된 경우 이 오류를 받습니다.

시간이 지남에 따라 우리는 코드베이스에서 오래된 마이그레이션을 정리/병합하므로, GitLab을 모든 이전 버전에서 마이그레이션하는 것이 항상 가능하지는 않습니다.

어떤 경우에는 이 검사를 우회하고 싶을 수 있습니다. 예를 들어, MIN_SCHEMA_VERSION보다 높은 GitLab 스키마 버전을 사용하다가 더 이전의 마이그레이션으로 롤백한 경우입니다. 이런 경우, 다시 마이그레이션하려면 SKIP_SCHEMA_VERSION_CHECK 환경 변수를 설정해야 합니다.

bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true

성능 문제

연결 풀링으로 연결 오버헤드 줄이기

새로운 데이터베이스 연결을 생성하는 것은 비용이 발생하며, PostgreSQL에서는 각 연결을 처리하기 위해 전체 프로세스를 포크해야 합니다. 연결이 매우 오랜 시간 동안 살아있다면 문제가 되지 않습니다. 그러나 여러 개의 작은 쿼리로 프로세스를 포크하는 것은 비용이 들 수 있습니다. 이를 방치하면 새로운 데이터베이스 연결의 정점이 성능 저하를 초래하거나 완전 중단으로 이어질 수 있습니다.

작고 짧은 데이터베이스 연결의 급증을 처리하는 인스턴스에 대한 검증된 솔루션은 PgBouncer를 연결 풀러로 구현하는 것입니다. 이 풀은 거의 오버헤드 없이 수천 개의 연결을 보유할 수 있습니다. 단점은 사용 패턴에 따라 최대 90% 이상의 성능 향상을 위해 소량의 대기 시간이 추가되는 것입니다.

PgBouncer는 다양한 설치에 맞게 미세 조정할 수 있습니다. 더 자세한 정보는 PgBouncer 미세 조정 문서를 참조하세요.

데이터베이스 통계 재생성을 위한 ANALYZE 실행

ANALYZE 명령은 많은 성능 문제를 해결하기 위한 좋은 첫 번째 접근 방식입니다. 테이블 통계를 재생성함으로써 쿼리 플래너는 보다 효율적인 쿼리 실행 경로를 생성합니다.

최신 통계는 결코 해가 되지 않습니다!

  • 리눅스 패키지의 경우 실행:

    gitlab-psql -c 'SET statement_timeout = 0; ANALYZE VERBOSE;'
    
  • SQL 프롬프트에서 실행:

    -- 기본 statement_timeout보다 더 오랫동안 실행될 가능성이 높기 때문에 필요
    SET statement_timeout = 0;
    ANALYZE VERBOSE;
    

ACTIVE 작업에 대한 데이터 수집

활성 쿼리는 데이터베이스에서 실제로 상당한 리소스를 소모하는 유일한 쿼리입니다.

이 쿼리는 모든 기존 활성 쿼리에서 메타 정보를 수집하며 다음을 포함합니다:

  • 그들의 연령
  • 출처 서비스
  • wait_event (대기 상태인 경우)
  • 기타 가능한 관련 정보:
-- 긴 쿼리는 일반적으로 필드를 수직으로 배열하면 읽기 쉽습니다
\x

SELECT
    pid
    ,datname
    ,usename
    ,application_name
    ,client_hostname
    ,backend_start
    ,query_start
    ,query
    ,age(now(), query_start) AS "age"
    ,state
    ,wait_event
    ,wait_event_type
    ,backend_type
FROM pg_stat_activity
WHERE state = 'active';

이 쿼리는 단일 스냅샷을 캡처하므로 환경이 응답하지 않는 동안 3-5번 쿼리를 실행하는 것을 고려하세요:

-- 출력을 파일로 리디렉션
-- 이 위치는 `gitlab-psql`이 쓸 수 있어야 합니다
\o /tmp/active1304.out
--
-- 이제 위의 쿼리를 실행하세요
--
-- 모든 출력은 파일로 전송됩니다 - 프롬프트가 =이면 실행된 것입니다
-- 출력 쓰기 취소
\o

이 Python 스크립트pg_stat_activity의 출력을 이해하기 쉽고 성능 문제와 연관시키기 쉬운 숫자로 파싱하는 데 도움이 될 수 있습니다.

느리게 보이는 쿼리 조사

쿼리가 너무 오랜 시간 동안 완료되지 않거나 데이터베이스 리소스를 과도하게 사용하는 경우,

EXPLAIN을 사용하여 쿼리 플래너가 이를 실행하는 방법을 확인하세요:

EXPLAIN (ANALYZE, BUFFERS) SELECT ... FROM ...

BUFFERS는 관련된 메모리 양도 대략적으로 보여줍니다. I/O가 문제를 일으킬 수 있으므로, EXPLAIN을 실행할 때 BUFFERS를 추가하는 것을 잊지 마세요.

데이터베이스가 때때로 성능이 좋고, 때때로 느린 경우, 두 상태에서 동일한 쿼리에 대한 이 출력을 캡처하세요.

인덱스 부풀기 조사

인덱스 부풀기는 일반적으로 눈에 띄는 성능 문제를 일으키지 않지만, 특히 자동 청소 문제가 있는 경우 높은 디스크 사용으로 이어질 수 있습니다.

아래 쿼리는 PostgreSQL 자체의 postgres_index_bloat_estimates 테이블에서 부풀기 비율을 계산하고, 결과를 비율 값으로 정렬합니다. PostgreSQL은 올바르게 작동하기 위해 일정량의 부풀기가 필요하므로, 약 25%는 여전히 표준 행동을 나타냅니다.

select  a.identifier, a.bloat_size_bytes, b.tablename, b.ondisk_size_bytes,
    (a.bloat_size_bytes/b.ondisk_size_bytes::float)*100 as percentage
from postgres_index_bloat_estimates a
join postgres_indexes b on a.identifier=b.identifier
where
   -- 비율 계산 시 0이 아닌지 확인
   a.bloat_size_bytes>0 and
   b.ondisk_size_bytes>1000000000
order by  percentage desc;

인덱스 재빌드

부풀어진 테이블을 확인하면, 아래 쿼리를 사용하여 해당 인덱스를 재빌드할 수 있습니다.

인덱스가 재빌드된 후 통계가 재설정될 수 있으므로, ANALYZE를 다시 실행해야 합니다.

SET statement_timeout = 0;
REINDEX TABLE CONCURRENTLY <table_name>;

아래 쿼리를 ; 뒤에 \watch 30을 추가하여 실행함으로써 인덱스 재빌드 프로세스를 모니터링하세요:

SELECT
  t.tablename, indexname, c.reltuples AS num_rows,
  pg_size_pretty(pg_relation_size(quote_ident(t.tablename)::text)) AS table_size,
  pg_size_pretty(pg_relation_size(quote_ident(indexrelname)::text)) AS index_size,
CASE WHEN indisvalid THEN 'Y'
  ELSE 'N'
END AS VALID
FROM pg_tables t
LEFT OUTER JOIN pg_class c ON t.tablename=c.relname
LEFT OUTER JOIN
  ( SELECT c.relname AS ctablename, ipg.relname AS indexname, x.indnatts AS
  number_of_columns, indexrelname, indisvalid FROM pg_index x
JOIN pg_class c ON c.oid = x.indrelid
JOIN pg_class ipg ON ipg.oid = x.indexrelid
JOIN pg_stat_all_indexes psai ON x.indexrelid = psai.indexrelid )
AS foo
ON t.tablename = foo.ctablename
WHERE
  t.tablename in ('<comma_separated_table_names>')
  ORDER BY 1,2; \watch 30