Matambudziko e batch query processing uye mhinduro dzawo (chikamu 1)

Matambudziko e batch query processing uye mhinduro dzawo (chikamu 1)Zvinenge zvese zvemazuva ano zvigadzirwa zvesoftware zvinosanganisira akati wandei masevhisi. Kazhinji, nguva refu dzekupindura dzeinterservice chiteshi inova sosi yematambudziko ekuita. Mhinduro yakajairwa kurudzi urwu rwedambudziko ndeyekurongedza akawanda interservice zvikumbiro mupakeji imwe, inonzi batching.

Kana ukashandisa batch processing, unogona kusafara nemigumisiro maererano nekushanda kana kujekesa kwekodhi. Iyi nzira haisi nyore kune anofona sezvaungafunga. Nokuda kwezvinangwa zvakasiyana uye mumamiriro ezvinhu akasiyana, mhinduro dzinogona kusiyana zvikuru. Ndichishandisa mienzaniso chaiyo, ini ndicharatidza zvakanakira nekuipira kwemaitiro akati wandei.

Chirongwa chekuratidzira

Kuti zvijeke, ngatitarisei muenzaniso weimwe masevhisi ari muapplication yandiri kushanda pairi parizvino.

Tsanangudzo yekusarudzwa kwepuratifomu kune mienzanisoDambudziko rekuita zvisina kunaka rakajairwa uye harikanganisi chero mitauro chaiyo kana mapuratifomu. Ichi chinyorwa chichashandisa Spring + Kotlin kodhi mienzaniso kuratidza matambudziko nemhinduro. Kotlin inonzwisiswa zvakaenzana (kana isinganzwisisike) kuJava uye C # vagadziri, mukuwedzera, iyo kodhi yakanyanya compact uye inonzwisisika kupfuura muJava. Kuita kuti zvive nyore kunzwisisa kune vakachena Java Developers, ini ndichadzivisa mashiripiti akasviba eKotlin uye ndinongoshandisa mashiripiti machena (pamweya weLombok). Pachave nemaitiro mashoma ekuwedzera, asi ivo vanonyatsoziva kune vese Java programmers se static nzira, saka iyi ichava diki shuga isingazoparadze kuravira kwendiro.
Pane sevhisi yemvumo yegwaro. Mumwe munhu anogadzira gwaro uye anoriendesa kuti rikurukurwe, panguva inogadziriswa, uye pakupedzisira gwaro rinobvumirana. Iyo yekubvumidza sevhisi pachayo haina chainoziva nezve magwaro: ingori nhaurirano yevanobvumidza ine madiki ekuwedzera mabasa atisingazotarise pano.

Saka, kune makamuri ekutaura (anoenderana nemagwaro) ane predefined seti yevatori vechikamu mune imwe neimwe yadzo. Sezviri muhurukuro dzenguva dzose, mameseji ane mavara uye mafaera uye anogona kuva mhinduro kana kumberi:

data class ChatMessage(
  // nullable так как появляется только после persist
  val id: Long? = null,
  /** Ссылка на автора */
  val author: UserReference,
  /** Сообщение */
  val message: String,
  /** Ссылки на аттачи */
  // из-за особенностей связки JPA+СУБД проще поддерживать и null, и пустые списки
  val files: List<FileReference>? = null,
  /** Если является ответом, то здесь будет оригинал */
  val replyTo: ChatMessage? = null,
  /** Если является пересылкой, то здесь будет оригинал */
  val forwardFrom: ChatMessage? = null
)

Faira uye mushandisi zvinongedzo zvinongedzo kune mamwe madomasi. Pano tinorarama seizvi:

typealias FileReference Long
typealias UserReference Long

Yemushandisi data inochengetwa muKeycloak uye yakadzoserwa kuburikidza neREST. Izvo zvakafanana zvinoenda kune mafaera: mafaera uye metainformation nezve iwo anogara mune yakaparadzana faira yekuchengetedza sevhisi.

Mafoni ese kune aya masevhisi ndeaya zvikumbiro zvinorema. Izvi zvinoreva kuti basa rekutakura zvikumbiro izvi rakakura kudarika nguva yazvinotora kuti zvigadziriswe nevechitatu-bato. Pamabhenji edu ebvunzo, yakajairika yekufona nguva yemasevhisi akadaro i100 ms, saka tichashandisa manhamba aya mune ramangwana.

Isu tinofanirwa kugadzira yakapusa REST controller kugamuchira iyo yekupedzisira N meseji neruzivo rwese rwunodiwa. Ndokunge, isu tinotenda kuti iyo meseji modhi iri kumberi yakada kufanana uye data rese rinoda kutumirwa. Musiyano uripo pakati peyekumberi-yekupedzisira modhi ndewekuti iyo faira nemushandisi inoda kuratidzwa mune yakadhindwa zvishoma fomu kuitira kuti iite zvinongedzo:

/** В таком виде отдаются ссылки на сущности для фронта */
data class ReferenceUI(
  /** Идентификатор для url */
  val ref: String,
  /** Видимое пользователю название ссылки */
  val name: String
)
data class ChatMessageUI(
  val id: Long,
  /** Ссылка на автора */
  val author: ReferenceUI,
  /** Сообщение */
  val message: String,
  /** Ссылки на аттачи */
  val files: List<ReferenceUI>,
  /** Если являтся ответом, то здесь будет оригинал */
  val replyTo: ChatMessageUI? = null,
  /** Если являтся пересылкой, то здесь будет оригинал */
  val forwardFrom: ChatMessageUI? = null
)

Tinofanira kuita zvinotevera:

interface ChatRestApi {
  fun getLast(nInt): List<ChatMessageUI>
}

Iyo UI postfix inoreva DTO modhi yekumberi, ndiko kuti, izvo zvatinofanira kushandira kuburikidza neREST.

Chingave chinoshamisa apa ndechekuti isu hatisi kupfuudza chero chat ID uye kunyangwe iyo ChatMessage/ChatMessageUI modhi haina imwe. Ndakaita izvi nemaune kuti ndirege kusanganisa kodhi yemienzaniso (iyo chats yakasarudzika, saka tinogona kufunga kuti isu tine imwe chete).

Kuderera kwefilosofiOse ari maviri kirasi yeChatMessageUI uye yeChatRestApi.getLast nzira dzinoshandisa iyo List data mhando asi chokwadi iri iyo yakarairwa Set. Izvi zvakaipa muJDK, saka kuzivisa kurongeka kwezvinhu padanho rekutarisa (kuchengetedza kurongeka paunenge uchiwedzera nekubvisa) hazvishande. Saka yave tsika yakajairika kushandisa Rondedzero mune zviitiko apo yakarairwa Seti inodiwa (kune zvakare LinkedHashSet, asi iyi haisi interface).
Kuganhurirwa kwakakosha: Tichafunga kuti hapana cheni refu dzemhinduro kana kutamiswa. Kureva kuti aripo, asi kureba kwawo hakupfuuri mameseji matatu. Iyo ketani yese yemeseji inofanirwa kuendeswa kumberi.

Kuti ugamuchire data kubva kune ekunze masevhisi kune anotevera APIs:

interface ChatMessageRepository {
  fun findLast(nInt): List<ChatMessage>
}
data class FileHeadRemote(
  val id: FileReference,
  val name: String
)
interface FileRemoteApi {
  fun getHeadById(idFileReference): FileHeadRemote
  fun getHeadsByIds(idSet<FileReference>): Set<FileHeadRemote>
  fun getHeadsByIds(idList<FileReference>): List<FileHeadRemote>
  fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
  val id: UserReference,
  val name: String
)
interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote>
  fun getUsersByIds(idList<UserReference>): List<UserRemote>
}

Zvinogona kuoneka kuti masevhisi ekunze anotanga kupa kugadzirwa kwebatch, uye mune ese ari maviri mavhezheni: kuburikidza neSeti (pasina kuchengetedza kurongeka kwezvinhu, nemakiyi akasiyana) uye kuburikidza Rondedzero (panogona kuve neduplicate - iyo odha yakachengetedzwa).

Mashandisirwo ari nyore

Naive implementation

Yekutanga yekusaziva kuita kweREST controller yedu inotaridzika seizvi muzviitiko zvakawanda:

class ChatRestController(
  private val messageRepository: ChatMessageRepository,
  private val userRepository: UserRemoteApi,
  private val fileRepository: FileRemoteApi
) : ChatRestApi {
  override fun getLast(nInt) =
    messageRepository.findLast(n)
      .map it.toFrontModel() }
  
  private fun ChatMessage.toFrontModel(): ChatMessageUI =
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = userRepository.getUserById(author).toFrontReference(),
      message = message,
      files = files?.let files ->
        fileRepository.getHeadsByIds(files)
          .map it.toFrontReference() }
      } ?: listOf(),
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
}

Zvose zvakajeka, uye ichi chikuru pamwe chete.

Isu tinoshandisa batch kugadzirisa uye tinogamuchira data kubva kune yekunze sevhisi mumabhechi. Asi chii chinoitika pakubudirira kwedu?

Pameseji yega yega, kufona kumwechete kuUserRemoteApi kuchaitwa kuti uwane data pandima yemunyori uye kufona kumwe kuFileRemoteApi kuti uwane mafaera ese akabatanidzwa. Zvinoita sezviri. Ngatitii mberiFrom uye replyTo minda yeChatMessage inowanikwa nenzira yekuti izvi hazvidi mafoni asina basa. Asi kuvashandura kuita ChatMessageUI kunotungamira mukudzokororwa, kureva kuti, macounter ekufona anogona kuwedzera zvakanyanya. Sezvatakamboona, ngatifungei kuti isu hatina yakawanda yesting uye cheni inogumira kumameseji matatu.

Nekuda kweizvozvo, isu tichawana kubva kumbiri kusvika kumatanhatu mafoni kuenda kune ekunze masevhisi pameseji uye imwe yeJPA kufona pasuru yese yemameseji. Nhamba yese yekufona ichasiyana kubva pa2*N+1 kusvika 6*N+1. Yakawanda sei iyi mumayuniti chaiwo? Ngatitii zvinotora mameseji makumi maviri kupa peji. Kuti uvagamuchire, zvinotora kubva ku20 kusvika ku4 s. Zvinotyisa! Ndinoda kuichengeta mukati me10 ms. Uye sezvo vairota kugadzira kupururudza kusina musono kumberi, zvinodikanwa zvekuita zveiyi magumo zvinogona kupetwa kaviri.

Pros:

  1. Iyo kodhi ipfupi uye inozvinyora-yega (chiroto chechikwata chekutsigira).
  2. Iyo kodhi iri nyore, saka pane anenge asina mikana yekupfura iwe pachako mutsoka.
  3. Batch processing haiite senge chinhu chisiri chienzi uye inosanganiswa mune iyo logic.
  4. Logic shanduko ichaitwa nyore uye ichave yemuno.

Minus:

Kuita kunotyisa nekuda kwemapakiti madiki.

Iyi nzira inogona kuoneka kazhinji mumasevhisi akareruka kana mune prototypes. Kana kumhanya kwekuita shanduko kwakakosha, hazvikodzere kuomesa sisitimu. Panguva imwecheteyo, kune yedu yakapusa sevhisi kuita kunotyisa, saka chiyero chekushandiswa kweiyi nzira yakamanikana.

Naive parallel processing

Iwe unogona kutanga kugadzirisa mameseji ese akafanana - izvi zvinokutendera kuti ubvise mutsara wekuwedzera munguva zvichienderana nehuwandu hwemeseji. Iyi haisiriyo nzira yakanaka nekuti inozokonzeresa muhombe wepamusoro webasa rekunze.

Kuita parallel processing iri nyore kwazvo:

override fun getLast(nInt) =
  messageRepository.findLast(n).parallelStream()
    .map it.toFrontModel() }
    .collect(toList())

Tichishandisa parallel meseji kugadzirisa, tinowana 300-700 ms zvine hungwaru, zviri nani pane nekuita zvisina basa, asi zvakadaro zvisiri kukurumidza zvakakwana.

Neiyi nzira, zvikumbiro kune mushandisiRepository uye fileRepository zvichaitwa synchronously, izvo zvisina kunyatso shanda. Kuti ugadzirise izvi, iwe uchafanirwa kushandura iyo yekufona logic zvakanyanya. Semuenzaniso, kuburikidza neCompletionStage (aka CompletableFuture):

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  CompletableFuture.supplyAsync {
    userRepository.getUserById(author).toFrontReference()
  }.thenCombine(
    files?.let {
      CompletableFuture.supplyAsync {
        fileRepository.getHeadsByIds(files).map it.toFrontReference() }
      }
    } ?: CompletableFuture.completedFuture(listOf())
  ) authorfiles ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }.get()!!

Zvinogona kuonekwa kuti kodhi yekutanga yakapfava yemepu yave kushoma kunzwisisika. Izvi zvinodaro nekuti taifanira kupatsanura mafoni kumasevhisi ekunze kubva panoshandiswa mhedzisiro. Izvi pachazvo hazvina kuipa. Asi kusanganisa mafoni hakutaridzike kwakanaka uye kwakafanana neyakajairwa reactive "noodle".

Kana ukashandisa coroutines, zvese zvinotaridzika zvakanyanya kunaka:

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  join(
    userRepository.getUserById(author).toFrontReference() },
    files?.let fileRepository.getHeadsByIds(files)
      .map it.toFrontReference() } } ?: listOf() }
  ).let (author, files) ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }

Kupi:

fun <ABjoin(a: () -> Ab: () -> B) =
  runBlocking(IO{
    awaitAll(async a() }async b() })
  }.let {
    it[0as to it[1as B
  }

Tichifunga, tichishandisa parallel processing, tichawana 200-400 ms, iyo yatove pedyo nezvatinotarisira.

Zvinosuruvarisa, kufanana kwakanaka kwakadaro hakupo, uye mutengo wekubhadhara une utsinye: nevashandisi vashomanana chete vanoshanda panguva imwe chete, zvikumbiro zvakawanda zvichawira pamasevhisi, ayo asingazogadziriswi nenzira yakafanana, saka isu ichadzokera kune yedu yakasuruvara 4 s.

Mhedzisiro yangu kana uchishandisa sevhisi yakadai ndeye 1300-1700 ms yekugadzirisa makumi maviri mameseji. Izvi zvinokurumidza kupfuura mukutanga kushandiswa, asi zvakadaro hazvigadzirisi dambudziko.

Mamwe mashandisirwo emibvunzo yakafananaKo kana masevhisi echitatu asingape batch processing? Semuenzaniso, iwe unogona kuvanza kushomeka kwebatch kugadzirisa kuita mukati meiyo interface nzira:

interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toSet())
  fun getUsersByIds(idList<UserReference>): List<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toList())
}

Izvi zvine musoro kana iwe uchitarisira kuona batch kugadzirisa mune ramangwana shanduro.
Pros:

  1. Ita zviri nyore meseji-yakavakirwa parallel processing.
  2. Zvakanaka scalability.

Cons:

  1. Iko kudikanwa kwekuparadzanisa kutora data kubva pakugadziriswa kwayo paunenge uchigadzirisa zvikumbiro kune akasiyana masevhisi mukufanana.
  2. Kuwedzera mutoro pane wechitatu-bato masevhisi.

Zvinogona kuonekwa kuti chiyero chekushandiswa chinenge chakangofanana neicho chekusaziva nzira. Zvine musoro kushandisa nzira yekukumbira yakafanana kana iwe uchida kuwedzera kuita kwesevhisi yako kakawanda nekuda kwekubata zvisina tsitsi kwevamwe. Mumuenzaniso wedu, kushanda kwakawedzera ne2,5 nguva, asi izvi zvakajeka hazvina kukwana.

caching

Iwe unogona kuita caching mumweya weJPA yemasevhisi ekunze, ndiko kuti, chitoro chakagashirwa zvinhu mukati mechikamu kuti urege kuzvigamuchira zvakare (kusanganisira panguva yekugadzira batch). Iwe unogona kugadzira cache dzakadaro iwe pachako, unogona kushandisa Chirimo neyayo @Cacheable, uyezve iwe unogona kugara uchishandisa yakagadzirira-yakagadzirwa cache senge EhCache nemaoko.

Dambudziko rinowanzoitika nderokuti cache inobatsira chete kana iine hits. Muchiitiko chedu, hits pandima yemunyori inowanzoitika (ngatitii, 50%), asi hapazove nekurova pamafaira zvachose. Iyi nzira ichapa humwe kuvandudzwa, asi haizoshandure zvakanyanya kuita (uye isu tinoda pundutso).

Intersession (kureba) cache inoda yakaoma invalidation logic. Kazhinji, iwe gare gare iwe unosvika pakugadzirisa matambudziko ekuita uchishandisa intersession cache, zviri nani.

Pros:

  1. Shandisa caching pasina kuchinja kodhi.
  2. Kuwedzera kubereka kakawanda (mune dzimwe nguva).

Cons:

  1. Mikana yekuderedzwa kwekuita kana ikashandiswa zvisizvo.
  2. Memory yakakura pamusoro, kunyanya ine cache refu.
  3. Complex invalidation, zvikanganiso izvo zvinozotungamira kune zvakaoma-ku-kubereka matambudziko munguva yekumhanya.

Kazhinji, ma cache anoshandiswa chete kukurumidza kugadzirisa matambudziko ekugadzira. Izvi hazvireve kuti havafanirwe kushandiswa. Nekudaro, iwe unofanirwa kugara uchivabata nekuchenjerera uye kutanga waongorora mhedzisiro yekuita, uye chete wozoita sarudzo.

Mumuenzaniso wedu, cache ichapa kuwedzera kwekuita kweanenge 25%. Panguva imwecheteyo, ma cache ane zvakawanda zvakaipira, saka ini handingazvishandise pano.

Migumisiro

Saka, isu takatarisa kuisirwa kwehupenzi kwesevhisi inoshandisa batch kugadzirisa, uye dzimwe nzira dziri nyore dzekumhanyisa.

Kubatsira kukuru kwenzira idzi dzose kuri nyore, kubva kune kune zvakawanda zvinofadza zvinoguma.

Dambudziko rinowanzoitika nenzira idzi ibasa rakashata, kunyanya nekuda kwehukuru hwemapakiti. Saka, kana izvi zvigadziriso zvisingakodzeri iwe, saka zvakakosha kuti utarise nzira dzakasimba.

Pane nzira mbiri huru dzaungatsvaga mhinduro:

  • basa reasynchronous rine data (inoda shanduko yeparadigm, saka haina kukurukurwa munyaya ino);
  • kuwedzera kwema batches uchichengetedza synchronous processing.

Kuwedzerwa kwemabheti kuchaderedza zvakanyanya nhamba yekunze mafoni uye panguva imwechete chengetedza iyo kodhi kodhi. Chikamu chinotevera chechinyorwa chichave chakatsaurirwa kune uyu musoro.

Source: www.habr.com

Voeg