J-한솔넷

AWS DMS (Database Migration Service)를 못믿어 데이터베이스 테이블 비교 코드를 만들어 보았습니다. 본문

카테고리 없음

AWS DMS (Database Migration Service)를 못믿어 데이터베이스 테이블 비교 코드를 만들어 보았습니다.

jhansol 2025. 7. 9. 00:34

안녕하세요?
정말 오랜만에 블로그에 글을 올립니다. 그 동안 바쁘다는 핑계로 기억을 박제하지 못했네요. ㅠㅠ

저는 요즘 개발 업무보다 회사의 운영 방침에 따라 AWS 인프라 관리, 마이그레이션관련 일에 몰두하고 있습니다. 개발 업무를 하고픈데, 업무가 이상하게 흘러가는 듯 합니다. 암튼 지금은 이 일을 하고 있습니다.

마이그레이션 작업에서 가장 신경이 쓰이는 부분이 운영 중인 데이터베이스를 다른 서버 또는 다른 계정으로 마이그레이션 하는 부분이라고 생각합니다. 지금 이 작업을 하고 있는데, 몇일 전 하나의 프로젝트(서비스) 데이터를 이동하는 중 데이터가 모두 복재가 되지 않아 소스에서 덤프를 뜨서 대상 데이터베이스에 복원한 적이 있습니다. 이 후로 DMS로 마이그레이션하고, 복제하는 작업을 신뢰할 수 없게 되었습니다.

그래서 만들어 봤습니다.

아래의 코드는 간단하게 ChatGPT를 이용하여 코드를 작성하고, 약간 수정한 것입니다. 잘 동작합니다.

const mariadb = require('mariadb');

const db1Config = {
  host: '127.0.0.1',
  port: 63306,            // ✅ 포트 번호 추가
  user: 'admin',
  password: 'xxxxxxxx!',
  database: 'xxxxxxxx',
};

const db2Config = {
  host: '127.0.0.1',
  port: 33306,            // ✅ 포트 번호 추가
  user: 'admin',
  password: 'xxxxxxx!',
  database: 'xxxxxxx',
};

async function getTableRowCounts(pool, database) {
  const conn = await pool.getConnection();
  try {
    const tables = await conn.query(`
      SELECT table_name
      FROM information_schema.tables
      WHERE table_schema = ?
    `, [database]);

    const counts = {};
    for (const row of tables) {
      const table = row.table_name;
      const result = await conn.query(`SELECT COUNT(*) as count FROM \`${table}\``);
      counts[table] = result[0].count;
    }
    return counts;
  } finally {
    conn.release();
  }
}

async function compareDatabases() {
  const pool1 = mariadb.createPool(db1Config);
  const pool2 = mariadb.createPool(db2Config);

  try {
    const [counts1, counts2] = await Promise.all([
      getTableRowCounts(pool1, db1Config.database),
      getTableRowCounts(pool2, db2Config.database),
    ]);

    const allTables = new Set([...Object.keys(counts1), ...Object.keys(counts2)]);
    let identical = true;

    for (const table of allTables) {
      const count1 = counts1[table];
      const count2 = counts2[table];

      if (count1 === undefined) {
        console.log(`❌ Table '${table}' missing in DB1`);
        identical = false;
      } else if (count2 === undefined) {
        console.log(`❌ Table '${table}' missing in DB2`);
        identical = false;
      } else if (count1 !== count2) {
        console.log(`❌ Table '${table}' row count differs: DB1=${count1}, DB2=${count2}`);
        identical = false;
      } else {
        console.log(`✅ Table '${table}' matches with ${count1} rows`);
      }
    }

    console.log('\n✅ Databases are ' + (identical ? 'identical' : 'different') + ' in structure and row count.');
  } catch (err) {
    console.error('Error:', err);
  } finally {
    await pool1.end();
    await pool2.end();
  }
}

compareDatabases();

그리고 package.json은 아래와 같습니다.

{
  "name": "db_compare",
  "version": "1.0.0",
  "description": "Compare database",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mariadb": "^3.4.4"
  }
}

잘 돌아갑니다. 그리고 테이블 이름과 테이블에 저장된 레코드 수가 모두 일치한다고 나요네요. 이로서 두 데이터베이스는 동일하다고 볼 수 있을 것 같습니다.

그리고 두 데이터베이스는 AWS 프라이빗 서브넷에 있어 SSH 명령을 이용하여 터널링하여 접속해서 체크했습니다.
결과는 아래와 같습니다.

node index.js
✅ Table 'dx_lecture_enrollments' matches with 0 rows
✅ Table 'member_companies' matches with 1 rows
✅ Table 'online_edu_lecture_study_logs' matches with 0 rows
✅ Table 'password_resets' matches with 0 rows
✅ Table 'performance_dx_job_transition_rates' matches with 0 rows
✅ Table 'experience_images' matches with 0 rows
✅ Table 'metabus_teaching_plans' matches with 0 rows
✅ Table 'ncs_code' matches with 13351 rows
✅ Table 'base_inspects' matches with 0 rows
✅ Table 'migrations' matches with 97 rows
✅ Table 'resume_activities' matches with 0 rows
✅ Table 'personal_access_tokens' matches with 0 rows
✅ Table 'online_edu_contents' matches with 0 rows
✅ Table 'type_inspects' matches with 0 rows
✅ Table 'failed_jobs' matches with 0 rows
✅ Table 'competences' matches with 0 rows
✅ Table 'metabus_classes' matches with 0 rows
✅ Table 'dx_lecture_schedules' matches with 17 rows
✅ Table 'univs' matches with 4 rows
✅ Table 'community_files' matches with 4 rows
✅ Table 'experience_folders' matches with 0 rows
✅ Table 'expert_certifications' matches with 0 rows
✅ Table 'users' matches with 13 rows
✅ Table 'member_company_representative_history' matches with 0 rows
✅ Table 'admins' matches with 4 rows
✅ Table 'metabus_class_times' matches with 0 rows
✅ Table 'report_logs' matches with 0 rows
✅ Table 'digital_badges' matches with 4 rows
✅ Table 'performance_adult_friendly_edu_indexes' matches with 0 rows
✅ Table 'inspect_questions' matches with 256 rows
✅ Table 'resume_languages' matches with 0 rows
✅ Table 'digital_badge_issued_users' matches with 0 rows
✅ Table 'univ_admins' matches with 6 rows
✅ Table 'recruit_job_codes' matches with 1297 rows
✅ Table 'communities' matches with 8 rows
✅ Table 'metabus_class_logs' matches with 0 rows
✅ Table 'performances' matches with 48 rows
✅ Table 'resume_certificates' matches with 0 rows
✅ Table 'metabus_matterports' matches with 4 rows
✅ Table 'expert_histories' matches with 0 rows
✅ Table 'resumes' matches with 0 rows
✅ Table 'resume_careers' matches with 0 rows
✅ Table 'cache' matches with 60 rows
✅ Table 'resume_educations' matches with 0 rows
✅ Table 'ncs_licenses' matches with 21299 rows
✅ Table 'inspect_answers' matches with 0 rows
✅ Table 'dx_lectures' matches with 4 rows
✅ Table 'performance_dx_governance_indexes' matches with 0 rows
✅ Table 'online_edu_content_media' matches with 0 rows
✅ Table 'self_inspects' matches with 0 rows
✅ Table 'performance_active_job_transition_indexes' matches with 0 rows
✅ Table 'metabus_class_users' matches with 0 rows
✅ Table 'cache_locks' matches with 0 rows
✅ Table 'resume_technologies' matches with 0 rows
✅ Table 'digital_badge_configs' matches with 3 rows
✅ Table 'performance_dx_stakeholder_satisfactions' matches with 0 rows
✅ Table 'dx_categories' matches with 21 rows
✅ Table 'sessions' matches with 59 rows
✅ Table 'member_company_certification_status' matches with 2 rows
✅ Table 'recruit_infos' matches with 64099 rows
✅ Table 'performance_dx_employed_startup_rates' matches with 0 rows
✅ Table 'resume_self_introductions' matches with 0 rows
✅ Table 'expert_experiences' matches with 0 rows
✅ Table 'experiences' matches with 0 rows
✅ Table 'resume_awards' matches with 0 rows
✅ Table 'community_comments' matches with 1 rows
✅ Table 'online_edu_lectures' matches with 0 rows
✅ Table 'experts' matches with 0 rows
✅ Table 'platform_histories' matches with 0 rows
✅ Table 'performance_dx_attainment_degree_rates' matches with 0 rows
✅ Table 'performance_dx_completes' matches with 0 rows
✅ Table 'ncs_recommend_keywords' matches with 1083 rows
✅ Table 'ncs_self_inspects' matches with 1114 rows

✅ Databases are identical in structure and row count.