お世話になります。
spring-boot 1.4
spring-data-jpa 1.10
を使用しています。
バッチ処理の速度改善に取り組んでいますが、100件ほど入れるinsertがbulk insertではないことに気づきました。
(この100件ほどのinsertはバッチ内で何度も実行されます。)
bulk insertに変更したいと思い、以下のページを読みましたが、
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-data-access
https://stackoverflow.com/questions/34228044/how-to-enable-batch-inserts-with-hibernate-and-spring-boot
https://www.baeldung.com/spring-data-jpa-batch-inserts
どれもentity managerを使うような仕組みでした。
そのように実装してもよいのですが、どちらかといえば、
saveメソッド(JpaRepository)
<S extends T> List<S> save(Iterable<S> entities);
の動きをbulk insertに変更できないかと考えています。
お知恵を貸していただきたいです。
よろしくお願いします。
---情報を追加しました。---
DB : PostgreSQL 9.5
エンティティクラスの主キー部分のソースコード
@Id
@SequenceGenerator( name = "hoge",
sequenceName = "piyo",
allocationSize = 1 )
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "hoge" )
@Column
private Long fooBarId;
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+3
質問内容の追記ありがとうございました。
下記の環境で、リポジトリのメソッドを使ってコレクションの永続化にバルクインサートが行われるか確認しました。
この調査で参考にしたサイトは『Batch Insert/Update with Hibernate/JPA』です。
また、ご質問内容のSpring Bootのバージョンと、今回検証に使用したSpring Bootのバージョンが大きく異なりますので、この回答があてにならない可能性がありますが、そのときはご了承ください。
環境
- Windows 10 Professional 1903
- OpenJDK 11.0.2
- Spring Boot 2.2.0
- Spring Data JPA 2.2.0
- Hibernate Core 5.4.6
- postgresql 42.2.8 (JDBC Driver)
データベース
データベースはPostgreSQL 9.6.1です。
memoテーブル
CREATE TABLE memo (
id bigserial NOT NULL,
title varchar(255) NOT NULL,
description text NOT NULL,
done boolean DEFAULT false NOT NULL,
updated timestamp without time zone DEFAULT current_timestamp NOT NULL,
CONSTRAINT memo_pkey PRIMARY KEY(id)
);
memoテーブルの主キー用シーケンスの増分数を100へ変更
ALTER SEQUENCE memo_id_seq INCREMENT BY 100;
Spring Boot
アプリケーション設定ファイル(application.yml)
spring:
datasource:
url: jdbc:postgresql://localhost:5432/test_db?reWriteBatchedInserts=true
username: test_user
password: test_user
hikari:
connection-test-query: select 1
connection-timeout: 10000
maximum-pool-size: 2
minimum-idle: 2
jpa:
open-in-view: true
show-sql: true
properties:
hibernate:
generate_statistics: true
order_inserts: true
format_sql: true
dialect: org.hibernate.dialect.PostgreSQL95Dialect
jdbc:
batch_size: 100
エンティティクラス
@Entity
@Table(name = "memo")
public class Memo implements Serializable {
private static final long serialVersionUID = 7104170344304358209L;
@Id
@SequenceGenerator(name = "memo_id_seq", sequenceName = "memo_id_seq", allocationSize = 100)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "memo_id_seq")
private Long id;
@Column(name = "title", nullable = false)
private String title;
@Column(name = "description", nullable = false)
private String description;
@Column(name = "done", nullable = false)
private Boolean done;
@Column(name = "updated", nullable = false)
private LocalDateTime updated;
// アクセサは省略
}
リポジトリ
public interface MemoRepository extends JpaRepository<Memo, Long> {
}
サービスクラス
MemoクラスのコレクションをリポジトリのsaveAllメソッドで永続化する部分の抜粋です。
Spring Data JPA 2.xからコレクションを永続化するメソッドはsaveAll
に変わっています。(Spring Data JPA 1.xではsave
)
Interface JpaRepository<T,ID> - 1.5.0.RELEASE
Interface JpaRepository<T,ID> - 2.2.0.RELEASE
@Transactional(readOnly = false)
@Override
public void save(List<Memo> entities) {
log.info("save all start");
memoRepository.saveAll(entities);
log.info("save all end");
}
また、比較用にエンティティマネージャを使った永続化のコードです。
@Transactional(readOnly = false)
@Override
public void save2(List<Memo> entities) {
log.info("save2 all start");
for (int i=0; i<entities.size(); i++) {
entityManager.persist(entities.get(i));
if (i> 0 && i % 100 == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
log.info("save2 all end");
}
動作確認
バルクインサートについて
リポジトリのsaveAllメソッドでもバルクインサートが行われるようにするには、下記2点の設定が必要でした。
- 1つ目は、バッチサイズ(
spring.jpa.properties.hibernate.jdbc.batch_size
)を設定すること。 - 2つ目は、JDBC URLに
reWriteBatchedInserts=true
を付けること。
2つ目についてはJDBCドライバレベルで影響が出る設定なので、こちら『Connecting to the Database』などでご確認ください。
比較用にエンティティマネージャを使った場合の動作確認も行いましたが、エンティティマネージャを使う方は、100件単位でflush/clearを行っています。(無駄なキャッシュをクリアするため)
リポジトリの方では行っていないのですが、メモリの使用量が気になる場合は同じような処理を入れた方がいいかもしれません。
バルクインサートが行われたかどうかの確認
PostgreSQLのログからバルクインサートが行われていることを確認しました。
LOG: 実行 <unnamed>: insert into memo (description, done, title, updated, id) values ($1, $2, $3, $4, $5),($6, $7, $8, $9, $10),($11, $12, $13, $14, $15),($16, $17, $18, $19, $20),($21, $22, $23, $24, $25),($26, $27, $28, $29, $30),($31, $32, $33, $34, $35),($36, $37, $38, $39, $40),($41, $42, $43, $44, $45),($46, $47, $48, $49, $50),($51, $52, $53, $54, $55),($56, $57, $58, $59, $60),($61, $62, $63, $64, $65),($66, $67, $68, $69, $70),($71, $72, $73, $74, $75),($76, $77, $78, $79, $80),($81, $82, $83, $84, $85),($86, $87, $88, $89, $90),($91, $92, $93, $94, $95),($96, $97, $98, $99, $100),($101, $102, $103, $104, $105),($106, $107, $108, $109, $110),($111, $112, $113, $114, $115),($116, $117, $118, $119, $120),($121, $122, $123, $124, $125),($126, $127, $128, $129, $130),($131, $132, $133, $134, $135),($136, $137, $138, $139, $140),($141, $142, $143, $144, $145),($146, $147, $148, $149, $150),($151, $152, $153, $154, $155),($156, $157, $158, $159, $160),($161, $162, $163, $164, $165),($166, $167, $168, $169, $170),($171, $172, $173, $174, $175),($176, $177, $178, $179, $180),($181, $182, $183, $184, $185),($186, $187, $188, $189, $190),($191, $192, $193, $194, $195),($196, $197, $198, $199, $200),($201, $202, $203, $204, $205),($206, $207, $208, $209, $210),($211, $212, $213, $214, $215),($216, $217, $218, $219, $220),($221, $222, $223, $224, $225),($226, $227, $228, $229, $230),($231, $232, $233, $234, $235),($236, $237, $238, $239, $240),($241, $242, $243, $244, $245),($246, $247, $248, $249, $250),($251, $252, $253, $254, $255),($256, $257, $258, $259, $260),($261, $262, $263, $264, $265),($266, $267, $268, $269, $270),($271, $272, $273, $274, $275),($276, $277, $278, $279, $280),($281, $282, $283, $284, $285),($286, $287, $288, $289, $290),($291, $292, $293, $294, $295),($296, $297, $298, $299, $300),($301, $302, $303, $304, $305),($306, $307, $308, $309, $310),($311, $312, $313, $314, $315),($316, $317, $318, $319, $320)
性能
以下はバッチサイズを100と設定した状態と設定しなかった状態で、Memoクラスのコレクションが1万,5万,10万件を永続化したときにかかった時間です。
件数 | リポジトリ (batch_size=100) | リポジトリ (batch_size未指定) | エンティティマネージャ (batch_size=100) | エンティティマネージャ (batch_size未指定) |
---|---|---|---|---|
1万件 | 1.075秒 | 3.044秒 | 0.925秒 | 2.918秒 |
5万件 | 3.74秒 | 14.081秒 | 3.269秒 | 13.483秒 |
10万件 | 6.862秒 | 27.644秒 | 6.282秒 | 26.519秒 |
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.20%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
rubytomato
2019/10/25 13:28
利用されているデータベースの種類とバージョン、バルクインサートしたいエンティティクラスの主キーの部分のソースコード(@Idを付けているフィールドのこと)を質問内容に追記してください。
motisen
2019/10/25 14:37
情報追記致しました。ご確認よろしくお願い致します。