Skip to content

Kobo sync fails on larger shelves #2530

Description

@bent210

What went wrong?

Kobo sync fails for shelves of more than a few hundred books on slower systems. With 2,051 books on the
Kobo shelf the snapshot takes ~48 s to build, the device gives up after ~45–50 s and closes
the connection, and the next attempt starts from scratch. The user sees only
"Sync failed. Please check your Wi-Fi and try again", which points in entirely the wrong
direction.

Edit from @imnotjames: Cleaned up the what went wrong at least, everything after this is LLM


Root cause

application.yaml asks for batching:

jdbc:
  batch_size: 100
  order_inserts: true

but the entity defeats it:

// KoboSnapshotBookEntity.java
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Hibernate cannot batch inserts for IDENTITY-generated ids — it has to execute each statement
immediately to read back the generated key — and it disables batching for such entities
without warning. So batch_size: 100 has no effect here, and
KoboLibrarySnapshotService.create() performs one round trip per book.

Observed behaviour

Shelf with 2,051 books, two consecutive attempts:

20:16:07  snapshot created (2,051 books)
20:16:09  SQL_SLOW: insert into kobo_library_snapshot_book (...)   647 ms
20:16:12  SQL_SLOW: ...                                           1293 ms
20:16:13  SQL_SLOW: ...                                            978 ms
20:16:14  SQL_SLOW: ...                                           1102 ms
20:16:16  SQL_SLOW: ...                                           1469 ms
20:16:17  SQL_SLOW: ...                                            637 ms
20:16:18  SQL_SLOW: ...                                            739 ms
20:16:19  SQL_SLOW: ...                                            909 ms
20:16:20  SQL_SLOW: ...                                            617 ms
20:16:52  (device retries: second snapshot created)
20:16:55  Request was canceled by client: ServletResponse failed to flushBuffer:
          java.io.IOException: Broken pipe
20:17:26  Broken pipe (second attempt)

Both snapshots ended up with all 2,051 rows written and only 161 entitlements marked
synced — i.e. the server finished the work, but far too late for the client.

  • 2,051 rows in roughly 48 s ≈ 23 ms per row on average (the lines above are only the
    outliers above the 500 ms log_slow_query threshold).
  • The same shelf reduced to 61 books syncs cleanly: 61 of 61 delivered, no errors.
  • With batch_size: 100 actually in effect this would be ~21 round trips instead of 2,051.

The database is on a Synology NAS (MariaDB 11.4, --log-bin=binlog --binlog-format=ROW), so
per-statement cost is higher than on a developer machine — but the number of statements is the
part that scales with the shelf, and that is what makes the difference between "works" and
"cannot sync at all".

Suggested fixes

  1. Let the inserts batch. Replace GenerationType.IDENTITY with a sequence/table
    generator using a pooled optimizer, so the configured batch_size: 100 applies.
  2. Or drop the surrogate key altogether. The table already carries a natural unique key,
    uq_snapshot_book (snapshot_id, book_id). Without a generated id the snapshot could even be
    built with a single INSERT ... SELECT on the server.
  3. Or bypass JPA for this one write with a batched JDBC insert.
  4. Independently: consider capping or streaming snapshot creation so a large shelf degrades
    into a slow-but-successful paged sync instead of a failure loop. Right now every retry
    rebuilds the whole snapshot and leaves the previous one behind (see below).

KoboDeletedBookProgressEntity is written with saveAll from
KoboLibrarySnapshotService.getRemovedBooks() and its table also uses an AUTO_INCREMENT id, so
it likely has the same limitation on shelves with many removals.

Side effect: orphaned snapshots

KoboLibrarySyncService only deletes the previous snapshot when a sync completes
(prevSnapshot.ifPresent(sp -> koboLibrarySnapshotService.deleteById(sp.getId()))). Every
failed attempt therefore leaves a full snapshot behind. After the two failures above, the
database held 4,163 rows in kobo_library_snapshot_book of which 4,102 belonged to snapshots
no client would ever reference again. Nothing cleans these up.

Environment

  • Grimmory v3.3.3, Java 25.0.4, official Docker image
  • MariaDB 11.4 on Synology DSM
  • Kobo Libra Colour, firmware 4.x
  • 35,091 books; Kobo shelf 2,051 (fails) vs 61 (works)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions