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.RealmResults
import io.realm.annotations.Index
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 content: String = "",
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.model.EventEntity
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
import io.realm.Sort

@ -37,6 +38,10 @@ fun RealmQuery<EventEntity>.last(from: Long? = null): EventEntity? {
.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> {
return EventEntity
.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.session.room.members.RoomDisplayNameResolver
import io.realm.Realm
import io.realm.kotlin.createObject
import timber.log.Timber
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 indexesToUpdate = changeSet.orderedCollectionChangeSet.changes + changeSet.orderedCollectionChangeSet.insertions
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 {
val room = rooms[it]
try {
manageRoom(realm, room)
insertRoom(realm, room)
} catch (e: Exception) {
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) {
return
}
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 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) {
monarchy.runTransactionSync { realm ->
// 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 eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)

RoomEntity
.where(realm, roomId).findFirst()
?.let { it.areAllMembersLoaded = true }
val chunk = stateEventsChunkHandler.handle(realm, roomId, eventsToInsert)
if (!roomEntity.chunks.contains(chunk)) {
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(
@Json(name = "start") val nextToken: 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()
)

View File

@ -7,9 +7,10 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
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.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.findWithNextToken
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.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.kotlin.createObject
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -73,15 +75,16 @@ class PaginationRequest(private val roomAPI: RoomAPI,
private fun insertInDb(receivedChunk: TokenChunkEvent, roomId: String) {
monarchy.runTransactionSync { realm ->
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)
?: ChunkEntity()
?: realm.createObject()

currentChunk.prevToken = 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 hasOverlapped = chunksOverlapped.isNotEmpty()

@ -90,14 +93,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
roomEntity.chunks.add(stateEventsChunk)
}

receivedChunk.chunk.forEach { event ->
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!currentChunk.events.contains(eventEntity)) {
currentChunk.events.add(eventEntity)
}
}
receivedChunk.events.addManagedToChunk(currentChunk)

if (prevChunk != null) {
currentChunk.events.addAll(prevChunk.events)
@ -106,7 +102,7 @@ class PaginationRequest(private val roomAPI: RoomAPI,
} else if (hasOverlapped) {
chunksOverlapped.forEach { overlapped ->
overlapped.events.forEach { event ->
if (!currentChunk.events.contains(event)) {
if (!currentChunk.events.fastContains(event)) {
currentChunk.events.add(event)
}
}

View File

@ -17,21 +17,22 @@ class ReadReceiptHandler {
if (content == null) {
return emptyList()
}
return content
val readReceipts = content
.flatMap { (eventId, receiptDict) ->
receiptDict
.filterKeys { it == "m.read" }
.flatMap { (_, userIdsDict) ->
userIdsDict.map { (userId, paramsDict) ->
val ts = paramsDict.filterKeys { it == "ts" }
.values
.firstOrNull() ?: 0.0
.values
.firstOrNull() ?: 0.0
val primaryKey = roomId + userId
ReadReceiptEntity(primaryKey, userId, eventId, roomId, ts)
}
}
}
.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.EventType
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.RoomEntity
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.RoomSyncSummary
import io.realm.Realm
import io.realm.kotlin.createObject


internal class RoomSyncHandler(private val monarchy: Monarchy,
@ -30,12 +31,12 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,

fun handleRoomSync(handlingStrategy: HandlingStrategy) {
monarchy.runTransactionSync { realm ->
val roomEntities = when (handlingStrategy) {
is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) }
val rooms = when (handlingStrategy) {
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.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) {
@ -53,13 +54,13 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomId: String,
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) {
roomEntity.chunks.deleteAllFromRealm()
}


roomEntity.membership = MyMembership.JOINED

if (roomSync.summary != null) {
@ -72,7 +73,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
}
}
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)) {
roomEntity.chunks.add(chunkEntity)
}
@ -88,7 +89,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomEntity.roomId = roomId
roomEntity.membership = MyMembership.INVITED
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)) {
roomEntity.chunks.add(chunkEntity)
}
@ -110,7 +111,7 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
roomSummary: RoomSyncSummary) {

val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: RoomSummaryEntity(roomId)
?: RoomSummaryEntity(roomId)

if (roomSummary.heroes.isNotEmpty()) {
roomSummaryEntity.heroes.clear()
@ -125,30 +126,22 @@ internal class RoomSyncHandler(private val monarchy: Monarchy,
realm.insertOrUpdate(roomSummaryEntity)
}

private fun handleListOfEvent(realm: Realm,
roomId: String,
eventList: List<Event>,
prevToken: String? = null,
nextToken: String? = null,
isLimited: Boolean = true): ChunkEntity {
private fun handleTimelineEvents(realm: Realm,
roomId: String,
eventList: List<Event>,
prevToken: String? = null,
nextToken: String? = null,
isLimited: Boolean = true): ChunkEntity {

val chunkEntity = if (!isLimited) {
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)
} else {
val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! }
ChunkEntity.findAllIncludingEvents(realm, eventIds).firstOrNull()
} ?: ChunkEntity().apply { this.prevToken = prevToken }
} ?: realm.createObject<ChunkEntity>().apply { this.prevToken = prevToken }

chunkEntity.nextToken = nextToken

eventList.forEach { event ->
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!chunkEntity.events.contains(eventEntity)) {
chunkEntity.events.add(eventEntity)
}
}
eventList.addManagedToChunk(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.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.query.findWithNextToken
import io.realm.Realm
import io.realm.kotlin.createObject

class StateEventsChunkHandler {

fun handle(realm: Realm, roomId: String, stateEvents: List<Event>): ChunkEntity {
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 ->
val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it)
}
if (!chunkEntity.events.contains(eventEntity)) {
chunkEntity.events.add(eventEntity)
}
}
stateEvents.addManagedToChunk(chunkEntity)
return chunkEntity
}


}
}