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

이 섹션은 몇 가지 머리를 맞대기하며 직면하는 데이터베이스 문제에 대한 참고로 사용할 수있는 복사-붙이기를 제공하는 데 도움을 주고자 합니다.

첫 번째 단계는 슬랙에서 오류를 검색하거나 구글에서 GitLab <나의 에러>를 검색하는 것입니다.

사용 가능한 RAILS_ENV:

  • production (일반적으로 본인의 메인 GDK 데이터베이스용이 아니지만 Omnibus와 같은 다른 설치에 필요할 수 있음).
  • development (본인의 주요 GDK 데이터베이스입니다).
  • test (RSpec와 같은 테스트에 사용됨).

모든 것을 삭제하고 처음부터 시작

모든 것을 삭제하고 빈 DB로부터 처음부터 시작하려면(약 1분 소요):

bundle exec rake db:reset RAILS_ENV=development

빈 DB에 샘플 데이터를 시드하려면(약 4분 소요):

bundle exec rake dev:setup

모든 것을 삭제하고 샘플 데이터로부터 처음부터 시작하려면(약 4분 소요). 또한 db:reset을 실행하고 DB별 마이그레이션을 실행합니다:

bundle exec rake db:setup RAILS_ENV=development

만약 테스트 DB에서 문제가 발생한다면 중요한 데이터가 없기 때문에 모든 것을 안전하게 삭제해도 됩니다:

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 데이터베이스에 액세스하고 탐색하려면 PostgreSQL 확장을 사용하여 데이터베이스 연결을 생성하십시오.

필수 구성 요소:

데이터베이스 연결 생성:

  1. 활동 표시줄에서 PostgreSQL Explorer 아이콘을 선택합니다.
  2. 열린 창에서 +를 선택하여 새 데이터베이스 연결을 추가합니다:
  3. 데이터베이스의 호스트 이름을 입력합니다. GDK 디렉토리의 PostgreSQL 폴더 경로를 사용합니다.
    • 예: /dev/gitlab-development-kit/postgresql
  4. 인증으로 사용할 PostgreSQL 사용자를 입력합니다. PostgreSQL 설치 중에 다르게 지정되지 않은 경우 로컬 사용자 이름을 사용합니다. PostgreSQL 사용자 이름을 확인하려면:
    1. gitlab 디렉토리에 있는지 확인합니다.
    2. PostgreSQL 데이터베이스에 액세스합니다. rails db를 실행합니다. 출력은 다음과 같아야합니다:

         psql (14.9)
         Type "help" for help.
      
         gitlabhq_development=#
      
    3. 반환된 PostgreSQL 프롬프트에서 \conninfo를 실행하여 연결된 사용자와 연결을 구성하는 데 사용된 포트를 표시합니다. 예를들어:

         You are connected to database "gitlabhq_development" as user "root" on host "localhost" (address "127.0.0.1") at port "5432".
      
  5. PostgreSQL 사용자의 암호를 입력하라는 메시지가 표시되면 설정한 암호를 입력하거나 필드를 비워 둡니다.
    • 포스트그레 서버가 실행 중인 동일한 기계에 로그인했으므로 암호가 필요하지 않습니다.
  6. 연결할 포트 번호를 입력합니다. 기본 포트 번호는 5432입니다.
  7. SSL 연결을 사용하시겠습니까? 필드에서 설치에 적합한 연결을 선택합니다. 옵션은 다음과 같습니다:
    • 보안 연결 사용
    • 표준 연결(기본값)
  8. 선택적으로 연결할 데이터베이스 필드에 gitlabhq_development를 입력합니다.
  9. 데이터베이스 연결의 표시 이름 필드에 gitlabhq_development를 입력합니다.

gitlabhq_development 데이터베이스 연결은 이제 PostgreSQL Explorer 창에 표시됩니다. 화살표를 사용하여 GDK 데이터베이스의 내용을 확장하고 탐색할 수 있습니다.

연결할 수 없는 경우, GDK가 실행 중인지 확인하고 다시 시도하십시오. 더 자세한 PostgreSQL Explorer 확장 프로그램 사용 방법은 사용법 섹션을 확인합니다.

FAQ

ActiveRecord::PendingMigrationError 및 Spring

Spring pre-loader를 사용하여 Specs를 실행하는 경우, 테스트 데이터베이스가 손상된 상태가 될 수 있습니다. 마이그레이션 실행이나 테스트 데이터베이스의 삭제/재설정이 효과가 없을 수 있습니다.

$ bundle exec spring rspec some_spec.rb
...
Failure/Error: ActiveRecord::Migration.maintain_test_schema!

ActiveRecord::PendingMigrationError:


  Migrations are pending. To resolve this issue, run:

    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 examples, 0 failures, 1 error occurred outside of examples

이를 해결하기 위해 Spring 서버와 애플리케이션을 종료할 수 있습니다.

$ 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 명령은 많은 성능 문제를 해결하는 데 좋은 첫 번째 접근 방법입니다. 테이블 통계를 재생성함으로써 쿼리 플래너는 더 효율적인 쿼리 실행 경로를 생성합니다.

최신의 통계는 결코 해로울 일이 없습니다!

  • Linux 패키지의 경우 다음을 실행하십시오:

    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를 추가해야 합니다.

데이터베이스가 때때로 성능이 우수하고 때로는 느릴 때에는 동일한 쿼리에 대한 출력을 캡처합니다.

인덱스 블로트 조사

인덱스 블로트는 일반적으로 뚜렷한 성능 문제를 일으키지는 않지만 autovacuum 문제가 있는 경우 특히 디스크 사용량이 높아질 수 있습니다.

아래 쿼리는 PostgreSQL의 postgres_index_bloat_estimates 테이블에서 블로트 백분율을 계산하고 결과를 백분율 값으로 정렬합니다. PostgresSQL은 올바르게 작동하려면 일정 수준의 블로트가 필요하기 때문에 약 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