Skip to content

Conversation

@sanak
Copy link
Owner

@sanak sanak commented Jan 18, 2024

以前、 feature/switch-better-sqlite3-to-typeorm ブランチで試行していたものを整理の上、JestのテストやPostgreSQL/MySQLで最低限の動作が通るようにしたものになります。

TODO:

  • システムテストで発生する Entityのimportエラー の解消
  • PostgreSQLのサポート
    • ※初期データベース構築に30分ほどかかります。
    • 確認手順:
      # .env.example ファイルを .env にコピー後、パラメータを適宜調整
      $ cp .env.example .env
      # PostgreSQLデータベースを作成
      $ createdb -U postgres ba000001
      # downloadコマンドで初期データベース構築
      $ npm start download
  • TypeORM対応前からのマイグレーションサポート(システムテストの マイグレーションエラー の解消)
  • MySQLのサポート
    • ※初期データベース構築に30分ほどかかります。
    • ~/.my.cnf で以下の設定後、mysqlサービス再起動が必要です。
      [mysqld]
      sql_mode = "ANSI_QUOTES,PIPES_AS_CONCAT"
    • 確認手順:
      # .env.example ファイルを .env にコピー後、mysql側のコメントに切り替え、パラメータを適宜調整
      $ cp .env.example .env
      # MySQLデータベースを作成
      $ mysql -u root -p
      :
      > CREATE DATABASE ba000001;
      > exit
      # downloadコマンドで初期データベース構築
      $ npm start download
    • 座標値が小数点以下3-4桁に丸められてしまう不具合の対応
  • PostgreSQL/MySQLのdownloadコマンドパフォーマンス改善
  • 対応した内容の整理
  • アップストリームへのリンク

@sanak
Copy link
Owner Author

sanak commented Jan 23, 2024

MySQLで座標値が小数点以下3-4桁に丸められてしまう不具合については、以下のQiita記事、TypeORMドキュメントを参考に、 float 型から double precision 型に変更

合わせて、 integer で整数値に8桁を利用していたものの、以下のアドレス・ベース・レジストリデータ項目定義書(第1版)では、 整数 形式の最大値が13となっていたため、SQLite、PostgreSQL、MySQLのいずれでもサポートされている smallint 型に変更

ただし、上記対応の影響かで、PostgreSQL/MySQLの初期データベース構築にさらに+1時間程度の時間がかかるようになっているかもしれません。


(可能であれば) PostgreSQL/MySQLのdownloadコマンドパフォーマンス改善

上記については、SQLite、PostgreSQL、MySQLでサポートされている複数行インサートを週末目処で少し試してみます。

Comment on lines 155 to 151
// awaitがあるため、forEachでなくforループで回す
for (let i = 0; i < paramsList.length; i++) {
const params = paramsList[i];
await queryRunner.connection.query(preparedSql, params);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここしか見ていないので、分かりませんが、たぶん、こんな感じでできませんかねー。

const tasks = paramsList.map(params => {
  return queryRunner.connection.query(preparedSql, params);
})
Promise.all(tasks);

preparedSql が大きかったりすると、シリアルに実行したほうが良かったりしますが。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maskatsum こちらにつきまして、アドバイス頂き、大変ありがとうございます!🙇
今朝の時点で、手元のPC (M1 MacBook Pro)で、PostgreSQLで約2時間、MySQLで約4時間かかっていたものが、
上記の対応のみで、いずれも30分程度で処理完了するようになりました。😊

  • 修正前:
    • PostgreSQL: npm start download 1050.98s user 637.60s system 22% cpu 2:02:43.84 total
    • MySQL: npm start download 2806.36s user 1274.25s system 27% cpu 4:06:54.28 total
  • 修正後:
    • PostgreSQL: npm start download 897.74s user 700.45s system 84% cpu 31:34.43 total
    • MySQL: npm start download 942.74s user 273.01s system 54% cpu 37:28.01 total

Comment on lines +25 to +30
if (dsType === 'postgres') {
tempSql = tempSql.replace(matchedInsertOrReplace[0], 'INSERT INTO');
tempSql = tempSql.replace(/-- /g, '');
} else if (dsType === 'mysql') {
tempSql = tempSql.replace(matchedInsertOrReplace[0], 'REPLACE INTO');
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここは switch で分けたいですね。
あと dsType が string になっていますが、Enum にしたいですねー。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
別ブランチ上で作業中ですが、一旦下記コミットで対応しています。
(Enumに関しては良く分かりませんでしたので一旦保留としています。明日確認させてください。)
a8cb67f

Comment on lines +35 to +54
if (matchedJSONFunctions) {
for (let i = 0; i < matchedJSONFunctions.length; i++) {
const matchedJSONFucntion = matchedJSONFunctions[i];
if (matchedJSONFucntion.toLowerCase() === 'json_group_array') {
if (dsType === 'postgres') {
tempSql = tempSql.replace(matchedJSONFucntion, 'json_agg');
} else if (dsType === 'mysql') {
tempSql = tempSql.replace(matchedJSONFucntion, 'json_arrayagg');
}
} else if (matchedJSONFucntion.toLowerCase() === 'json_object') {
if (dsType === 'postgres') {
tempSql = tempSql.replace(matchedJSONFucntion, 'json_build_object');
}
}
}
}
return {
preparedSql: tempSql,
paramKeys: keys,
};
Copy link

@maskatsum maskatsum Jan 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こんな感じはどうですか?

// ネスティングはなるべく浅くすると良いです。コードの可読性が上がります。
if (!matchedJSONFunctions) {
  return {
    preparedSql: tempSql,
    paramKeys: keys,
  };
}

matchedJSONFunctions.forEach(matchedJSONFucntion => {
  const functionName = matchedJSONFucntion.toLowerCase();
  switch (`${functionName}-${dsType}`) {
    case 'json_group_array-postgress':
      tempSql = tempSql.replace(matchedJSONFucntion, 'json_agg');
      break;
  
    case 'json_group_array-mysql':
      tempSql = tempSql.replace(matchedJSONFucntion, 'json_arrayagg');
      break;

    case 'json_object-postgres':
      tempSql = tempSql.replace(matchedJSONFucntion, 'json_build_object');
      break;

    default:
      break;
  }
});

return {
  preparedSql: tempSql,
  paramKeys: keys,
};

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
別ブランチ上で作業中ですが、上記修正をそのまま採用させて頂き、以下のコミットで対応しています。
1de8fff

@sanak
Copy link
Owner Author

sanak commented Jan 24, 2024

@maskatsum 色々とアドバイス頂き、ありがとうございます! 🙇🙏
今晩、確認するようにいたしますので、よろしくお願いいたします。

1/25追記: 取り急ぎ、1つ目のコメント箇所に対応いたしました。残るコメントについては、週末にかけて確認するようにいたします。 🙇

@maskatsum
Copy link

ご対応ありがとうございますー。週末の打ち合わせでお話させていただくのを楽しみにしております。

Copy link

@maskatsum maskatsum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

明日、細かく説明しますが、気づいた点をコメントさせていただきますー

let tempSql = sql;
const keys = [];
const dsType = ds.options.type;
const matchedParams = tempSql.match(/@[a-zA-Z_0-9]+/g);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempSql.match(/@[a-zA-Z_0-9]+/g);

の部分は、RegExpEx.create を使っていただくと、正規表現のインスタンスがキャッシュされます。

また、@ は、設定値から来るものなので、ハードコーディングしてしまうと設定を変更した場合に反映されなくなってしまいます。

export const DASH: string = '@';

なので以下のようにしていただくのが良いです

tempSql.match(RegExpEx.create(`${DASH}[a-zA-Z_0-9]+`, 'g'));

if (matchedParams) {
for (let i = 0; i < matchedParams.length; i++) {
const matchedParam = matchedParams[i];
const paramName = matchedParam.replace('@', '');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

matchedParam.replace('@', '');

matchedParam.replace(DASH, '');

ですねー。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
別ブランチで作業中ですが、一つ上のRegExpExと合わせて、以下のコミットで対応しています。
a971ad7

Comment on lines +17 to +33
if (matchedInsertValues) {
for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
const placeHolders = [];
for (let paramIdx = 0; paramIdx < paramsCount; paramIdx++) {
const placeHolder =
dsType === 'postgres'
? `$${rowIdx * paramsCount + paramIdx + 1}`
: '?';
placeHolders.push(placeHolder);
}
rows.push(`(${placeHolders.join(',')})`);
}
tempSql = tempSql.replace(
matchedInsertValues[0],
'VALUES ' + rows.join(',')
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここのネストが深い部分は解消したいですねー。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

17行目の if (matchedInsertValues) { のブロックで1階層は減らせそうですが、

  if (!matchedInsertValues) {
    return tempSql;
  }

  for (let rowIdx = 0; rowIdx < rowCount; rowIdx++) {
    const placeHolders = [];
    for (let paramIdx = 0; paramIdx < paramsCount; paramIdx++) {
      const placeHolder =
        dsType === 'postgres' ? `$${rowIdx * paramsCount + paramIdx + 1}` : '?';
      placeHolders.push(placeHolder);
    }
    rows.push(`(${placeHolders.join(',')})`);
  }
  tempSql = tempSql.replace(matchedInsertValues[0], 'VALUES ' + rows.join(','));

さらにrow/paramの2階層ループのネストを減らそうとなると、難しそうな気がしました。

書き方にあまり慣れていませんが、以下のサイトなどを参考に、forループを短縮して書くことは
可能なようでしたが、どのレベルまで対応するかは、明日、確認させてください。
https://stackoverflow.com/questions/3746725/how-to-create-an-array-containing-1-n

const rows = [...Array(rowCount).keys()].map(rowIdx => {
    const placeHolders = [...Array(paramsCount).keys()].map(paramIdx => {
        return dsType === 'postgres' ? `$${rowIdx * paramsCount + paramIdx + 1}` : '?';
    });
    return `(${placeHolders.join(',')})`;
});

Comment on lines +233 to +224
await queryRunner.query('DROP INDEX IF EXISTS "dataset_key"');
await queryRunner.query('DROP TABLE IF EXISTS "dataset_old"');
await queryRunner.query('DROP INDEX IF EXISTS "metadata_key"');
await queryRunner.query('DROP TABLE IF EXISTS "metadata_old"');
await queryRunner.query('DROP INDEX IF EXISTS "rsdtdsp_rsdt_code"');
await queryRunner.query('DROP TABLE IF EXISTS "rsdtdsp_rsdt_old"');
await queryRunner.query('DROP INDEX IF EXISTS "rsdtdsp_blk_code"');
await queryRunner.query('DROP TABLE IF EXISTS "rsdtdsp_blk_old"');
await queryRunner.query('DROP INDEX IF EXISTS "town_code"');
await queryRunner.query('DROP TABLE IF EXISTS "town_old"');
await queryRunner.query('DROP INDEX IF EXISTS "city_code"');
await queryRunner.query('DROP TABLE IF EXISTS "city_old"');
await queryRunner.query('DROP INDEX IF EXISTS "pref_code"');
await queryRunner.query('DROP TABLE IF EXISTS "pref_old"');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この辺、こんな感じに、1queryにまとめられるような気がします(試してないですー)。

await queryRunner.query(`
  DROP INDEX IF EXISTS "dataset_key";
  DROP TABLE IF EXISTS "dataset_old";
  DROP INDEX IF EXISTS "metadata_key";
  DROP TABLE IF EXISTS "metadata_old";
  ....
  
  DROP TABLE IF EXISTS "pref_old";
`);

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
npm run test:system で上記の1クエリに集約できることを確認しました。
bd4193a

マイグレーションファイル内の他の箇所についても集約した方が良いか、明日、確認させてください。

@sanak sanak force-pushed the feature/97-typeorm-trial branch from a850862 to 05f4bb3 Compare February 3, 2024 16:39
@sanak
Copy link
Owner Author

sanak commented Jul 31, 2024

アップストリームのv2.0.0(地番レベル対応分)で、SQLiteデータベースが複数(各市区町村毎)に生成される形となったことに伴い、TypeORMでの対応が難しくなったようでしたので、こちらのPRは #4 と合わせてクローズします。
digital-go-jp#97 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants