Adding events to chunk is now faster by using query to check contains and no primary key

This commit is contained in:
ganfra 2018-10-26 12:31:47 +02:00
parent 7ba67d6c2b
commit e942b54b56
10 changed files with 84 additions and 66 deletions

View file

@ -0,0 +1,18 @@
package im.vector.matrix.android.internal.database.helper
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.fastContains
fun List<Event>.addManagedToChunk(chunkEntity: ChunkEntity) {
if (!chunkEntity.isManaged) {
throw IllegalStateException("Chunk entity should be managed to use fast contains")
}
this.forEach { event ->
val eventEntity = event.asEntity()
if (!chunkEntity.events.fastContains(eventEntity)) {
chunkEntity.events.add(eventEntity)
}
}
}

View file

@ -2,10 +2,10 @@ package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.annotations.Index
import io.realm.annotations.LinkingObjects import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
open class EventEntity(@PrimaryKey var eventId: String = "", open class EventEntity(@Index var eventId: String = "",
var type: String = "", var type: String = "",
var content: String = "", var content: String = "",
var prevContent: String? = null, var prevContent: String? = null,

View file

@ -5,6 +5,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import io.realm.Realm import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery import io.realm.RealmQuery
import io.realm.Sort import io.realm.Sort
@ -37,6 +38,10 @@ fun RealmQuery<EventEntity>.last(from: Long? = null): EventEntity? {
.findFirst() .findFirst()
} }
fun RealmList<EventEntity>.fastContains(eventEntity: EventEntity): Boolean {
return this.where().equalTo("eventId", eventEntity.eventId).findFirst() != null
}
fun EventEntity.Companion.findAllRoomMembers(realm: Realm, roomId: String): Map<String, RoomMember> { fun EventEntity.Companion.findAllRoomMembers(realm: Realm, roomId: String): Map<String, RoomMember> {
return EventEntity return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER) .where(realm, roomId, EventType.STATE_ROOM_MEMBER)

View file

@ -14,6 +14,7 @@ import im.vector.matrix.android.internal.database.query.last
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver import im.vector.matrix.android.internal.session.room.members.RoomDisplayNameResolver
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -46,28 +47,28 @@ internal class RoomSummaryUpdater(private val monarchy: Monarchy,
val rooms = changeSet.realmResults.map { it.asDomain() } val rooms = changeSet.realmResults.map { it.asDomain() }
val indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions val indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions
monarchy.writeAsync { realm -> monarchy.writeAsync { realm ->
manageRoomList(realm, rooms, indexesToUpdate) insertRoomList(realm, rooms, indexesToUpdate)
} }
} }
private fun manageRoomList(realm: Realm, rooms: List<Room>, indexes: IntArray) { private fun insertRoomList(realm: Realm, rooms: List<Room>, indexes: IntArray) {
indexes.forEach { indexes.forEach {
val room = rooms[it] val room = rooms[it]
try { try {
manageRoom(realm, room) insertRoom(realm, room)
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error occured when updating room summaries") Timber.e(e, "An error occured when updating room summaries")
} }
} }
} }
private fun manageRoom(realm: Realm, room: Room?) { private fun insertRoom(realm: Realm, room: Room?) {
if (room == null) { if (room == null) {
return return
} }
val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, room.roomId).findFirst()
?: RoomSummaryEntity(room.roomId) ?: realm.createObject(room.roomId)
val lastMessageEvent = EventEntity.where(realm, room.roomId, EventType.MESSAGE).last() val lastMessageEvent = EventEntity.where(realm, room.roomId, EventType.MESSAGE).last()
val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain() val lastTopicEvent = EventEntity.where(realm, room.roomId, EventType.STATE_ROOM_TOPIC).last()?.asDomain()

View file

@ -61,13 +61,17 @@ internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
private fun insertInDb(response: RoomMembersResponse, roomId: String) { private fun insertInDb(response: RoomMembersResponse, roomId: String) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
// We ignore all the already known members // We ignore all the already known members
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: throw IllegalStateException("You shouldn't use this method without a room")
val roomMembers = EventEntity.findAllRoomMembers(realm, roomId) val roomMembers = EventEntity.findAllRoomMembers(realm, roomId)
val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) } val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)
RoomEntity val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)
.where(realm, roomId).findFirst() if (!roomEntity.chunks.contains(chunk)) {
?.let { it.areAllMembersLoaded = true } roomEntity.chunks.add(chunk)
}
roomEntity.areAllMembersLoaded = true
} }
} }

View file

@ -8,6 +8,6 @@ import im.vector.matrix.android.api.session.events.model.Event
data class TokenChunkEvent( data class TokenChunkEvent(
@Json(name = "start") val nextToken: String? = null, @Json(name = "start") val nextToken: String? = null,
@Json(name = "end") val prevToken: String? = null, @Json(name = "end") val prevToken: String? = null,
@Json(name = "chunk") val chunk: List<Event> = emptyList(), @Json(name = "chunk") val events: List<Event> = emptyList(),
@Json(name = "state") val stateEvents: List<Event> = emptyList() @Json(name = "state") val stateEvents: List<Event> = emptyList()
) )

View file

@ -7,9 +7,10 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.helper.addManagedToChunk
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.fastContains
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.database.query.findWithNextToken import im.vector.matrix.android.internal.database.query.findWithNextToken
import im.vector.matrix.android.internal.database.query.findWithPrevToken import im.vector.matrix.android.internal.database.query.findWithPrevToken
@ -22,6 +23,7 @@ import im.vector.matrix.android.internal.session.room.model.TokenChunkEvent
import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler import im.vector.matrix.android.internal.session.sync.StateEventsChunkHandler
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.kotlin.createObject
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -73,15 +75,16 @@ class PaginationRequest(private val roomAPI: RoomAPI,
private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String) { private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: return@runTransactionSync ?: throw IllegalStateException("You shouldn't use this method without a room")
val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken) val currentChunk = ChunkEntity.findWithPrevToken(realm, roomId, receivedChunk.nextToken)
?: ChunkEntity() ?: realm.createObject()
currentChunk.prevToken = receivedChunk.prevToken currentChunk.prevToken = receivedChunk.prevToken
val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, receivedChunk.prevToken) val prevChunk = ChunkEntity.findWithNextToken(realm, roomId, receivedChunk.prevToken)
val eventIds = receivedChunk.chunk.filter { it.eventId != null }.map { it.eventId!! } val eventIds = receivedChunk.events.filter { it.eventId != null }.map { it.eventId!! }
val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds) val chunksOverlapped = ChunkEntity.findAllIncludingEvents(realm, eventIds)
val hasOverlapped = chunksOverlapped.isNotEmpty() val hasOverlapped = chunksOverlapped.isNotEmpty()
@ -90,14 +93,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
roomEntity.chunks.add(stateEventsChunk) roomEntity.chunks.add(stateEventsChunk)
} }
receivedChunk.chunk.forEach { event -> receivedChunk.events.addManagedToChunk(currentChunk)
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!currentChunk.events.contains(eventEntity)) {
currentChunk.events.add(eventEntity)
}
}
if (prevChunk != null) { if (prevChunk != null) {
currentChunk.events.addAll(prevChunk.events) currentChunk.events.addAll(prevChunk.events)
@ -106,7 +102,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
} else if (hasOverlapped) { } else if (hasOverlapped) {
chunksOverlapped.forEach { overlapped -> chunksOverlapped.forEach { overlapped ->
overlapped.events.forEach { event -> overlapped.events.forEach { event ->
if (!currentChunk.events.contains(event)) { if (!currentChunk.events.fastContains(event)) {
currentChunk.events.add(event) currentChunk.events.add(event)
} }
} }

View file

@ -17,7 +17,7 @@ class ReadReceiptHandler {
if (content == null) { if (content == null) {
return emptyList() return emptyList()
} }
return content val readReceipts = content
.flatMap { (eventId, receiptDict) -> .flatMap { (eventId, receiptDict) ->
receiptDict receiptDict
.filterKeys { it == "m.read" } .filterKeys { it == "m.read" }
@ -31,7 +31,8 @@ class ReadReceiptHandler {
} }
} }
} }
.apply { realm.insertOrUpdate(this) } realm.insertOrUpdate(readReceipts)
return readReceipts
} }
} }

View file

@ -4,7 +4,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.room.model.MyMembership import im.vector.matrix.android.api.session.room.model.MyMembership
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.helper.addManagedToChunk
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
@ -16,6 +16,7 @@ import im.vector.matrix.android.internal.session.sync.model.RoomSync
import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral import im.vector.matrix.android.internal.session.sync.model.RoomSyncEphemeral
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject
internal class RoomSyncHandler(private val monarchy: Monarchy, internal class RoomSyncHandler(private val monarchy: Monarchy,
@ -30,12 +31,12 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
fun handleRoomSync(handlingStrategy: HandlingStrategy) { fun handleRoomSync(handlingStrategy: HandlingStrategy) {
monarchy.runTransactionSync { realm -> monarchy.runTransactionSync { realm ->
val roomEntities = when (handlingStrategy) { val rooms = when (handlingStrategy) {
is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) } is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) }
is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) } is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) }
is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) } is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) }
} }
realm.insertOrUpdate(roomEntities) realm.insertOrUpdate(rooms)
} }
if (handlingStrategy is HandlingStrategy.JOINED) { if (handlingStrategy is HandlingStrategy.JOINED) {
@ -53,13 +54,13 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomId: String, roomId: String,
roomSync: RoomSync): RoomEntity { roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: RoomEntity(roomId) val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: RoomEntity(roomId)
if (roomEntity.membership == MyMembership.INVITED) { if (roomEntity.membership == MyMembership.INVITED) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
} }
roomEntity.membership = MyMembership.JOINED roomEntity.membership = MyMembership.JOINED
if (roomSync.summary != null) { if (roomSync.summary != null) {
@ -72,7 +73,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
} }
} }
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) { if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val chunkEntity = handleListOfEvent(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited) val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited)
if (!roomEntity.chunks.contains(chunkEntity)) { if (!roomEntity.chunks.contains(chunkEntity)) {
roomEntity.chunks.add(chunkEntity) roomEntity.chunks.add(chunkEntity)
} }
@ -88,7 +89,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.roomId = roomId roomEntity.roomId = roomId
roomEntity.membership = MyMembership.INVITED roomEntity.membership = MyMembership.INVITED
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) { if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleListOfEvent(realm, roomId, roomSync.inviteState.events) val chunkEntity = handleTimelineEvents(realm, roomId, roomSync.inviteState.events)
if (!roomEntity.chunks.contains(chunkEntity)) { if (!roomEntity.chunks.contains(chunkEntity)) {
roomEntity.chunks.add(chunkEntity) roomEntity.chunks.add(chunkEntity)
} }
@ -125,7 +126,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
realm.insertOrUpdate(roomSummaryEntity) realm.insertOrUpdate(roomSummaryEntity)
} }
private fun handleListOfEvent(realm: Realm, private fun handleTimelineEvents(realm: Realm,
roomId: String, roomId: String,
eventList: List<Event>, eventList: List<Event>,
prevToken: String? = null, prevToken: String? = null,
@ -137,18 +138,10 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
} else { } else {
val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! } val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! }
ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull() ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull()
} ?: ChunkEntity().apply { this.prevToken = prevToken } } ?: realm.createObject<ChunkEntity>().apply { this.prevToken = prevToken }
chunkEntity.nextToken = nextToken chunkEntity.nextToken = nextToken
eventList.addManagedToChunk(chunkEntity)
eventList.forEach { event ->
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!chunkEntity.events.contains(eventEntity)) {
chunkEntity.events.add(eventEntity)
}
}
return chunkEntity return chunkEntity
} }

View file

@ -2,27 +2,27 @@ package im.vector.matrix.android.internal.session.sync
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.database.DBConstants import im.vector.matrix.android.internal.database.DBConstants
import im.vector.matrix.android.internal.database.mapper.asEntity import im.vector.matrix.android.internal.database.helper.addManagedToChunk
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.query.findWithNextToken import im.vector.matrix.android.internal.database.query.findWithNextToken
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject
class StateEventsChunkHandler { class StateEventsChunkHandler {
fun handle(realm: Realm, roomId: String, stateEvents: List<Event>): ChunkEntity { fun handle(realm: Realm, roomId: String, stateEvents: List<Event>): ChunkEntity {
val chunkEntity = ChunkEntity.findWithNextToken(realm, roomId, DBConstants.STATE_EVENTS_CHUNK_TOKEN) val chunkEntity = ChunkEntity.findWithNextToken(realm, roomId, DBConstants.STATE_EVENTS_CHUNK_TOKEN)
?: ChunkEntity(prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN, nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN) ?: realm.createObject<ChunkEntity>()
.apply {
prevToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN
nextToken = DBConstants.STATE_EVENTS_CHUNK_TOKEN
}
stateEvents.forEach { event -> stateEvents.addManagedToChunk(chunkEntity)
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!chunkEntity.events.contains(eventEntity)) {
chunkEntity.events.add(eventEntity)
}
}
return chunkEntity return chunkEntity
} }
} }