Teeb meem ntawm batch query processing thiab lawv cov kev daws teeb meem (Part 1)

Teeb meem ntawm batch query processing thiab lawv cov kev daws teeb meem (Part 1)Yuav luag txhua yam khoom siv software niaj hnub no muaj ntau yam kev pabcuam. Feem ntau, cov lus teb ntev ntev ntawm kev sib koom ua ke tau dhau los ua qhov teeb meem ntawm kev ua haujlwm. Cov txheej txheem daws teeb meem zoo li no yog ntim ntau qhov kev thov kev pabcuam rau hauv ib pob, uas yog hu ua batching.

Yog tias koj siv batch ua, tej zaum koj yuav tsis zoo siab nrog cov txiaj ntsig ntawm kev ua tau zoo lossis kev pom meej meej. Txoj kev no tsis yooj yim rau tus hu li koj xav. Rau ntau lub hom phiaj thiab nyob rau hauv ntau qhov xwm txheej, kev daws teeb meem tuaj yeem sib txawv heev. Siv cov piv txwv tshwj xeeb, kuv yuav qhia qhov zoo thiab qhov tsis zoo ntawm ntau txoj hauv kev.

Kev ua qauv qhia

Kom meej meej, cia saib ib qho piv txwv ntawm ib qho ntawm cov kev pabcuam hauv daim ntawv thov uas kuv tab tom ua haujlwm tam sim no.

Kev piav qhia ntawm kev xaiv lub platform rau piv txwvQhov teeb meem ntawm kev ua haujlwm tsis zoo yog qhov dav dav thiab tsis cuam tshuam rau cov lus tshwj xeeb lossis lub platform. Kab lus no yuav siv Spring + Kotlin code piv txwv los qhia txog teeb meem thiab kev daws teeb meem. Kotlin yog qhov sib npaug ntawm kev nkag siab (lossis tsis nkag siab) rau Java thiab C # cov tsim tawm, ntxiv rau, cov cai yog qhov sib npaug thiab nkag siab ntau dua li hauv Java. Txhawm rau kom nkag siab yooj yim rau cov neeg tsim tawm Java ntshiab, Kuv yuav zam cov khawv koob dub ntawm Kotlin thiab tsuas yog siv cov khawv koob dawb (hauv lub siab ntawm Lombok). Yuav muaj ob peb txoj hauv kev txuas ntxiv, tab sis lawv yeej paub zoo rau txhua tus Java programmers raws li txoj hauv kev zoo li qub, yog li qhov no yuav yog cov suab thaj me me uas yuav tsis lwj qhov saj ntawm cov zaub mov.
Muaj ib qho kev pom zoo cov ntaub ntawv. Ib tug neeg tsim ib daim ntawv thiab xa mus rau kev sib tham, thaum lub sij hawm hloov kho, thiab thaum kawg cov ntaub ntawv tau pom zoo. Cov kev pabcuam kev pom zoo nws tus kheej tsis paub dab tsi txog cov ntaub ntawv: nws tsuas yog kev sib tham ntawm cov neeg pom zoo nrog cov haujlwm me me ntxiv uas peb yuav tsis xav txog ntawm no.

Yog li, muaj cov chav sib tham (suav nrog cov ntaub ntawv) nrog cov txheej txheem ua ntej ntawm cov neeg koom hauv lawv txhua tus. Raws li hauv kev sib tham tsis tu ncua, cov lus muaj cov ntawv nyeem thiab cov ntaub ntawv thiab tuaj yeem teb lossis xa mus:

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
)

Cov ntaub ntawv thiab cov neeg siv txuas yog txuas mus rau lwm qhov chaw. Peb nyob li no:

typealias FileReference Long
typealias UserReference Long

Cov neeg siv cov ntaub ntawv khaws cia hauv Keycloak thiab muab rov qab los ntawm REST. Tib yam mus rau cov ntaub ntawv: cov ntaub ntawv thiab cov ntaub ntawv metainformation txog lawv nyob rau hauv ib qho kev pabcuam khaws cia.

Txhua qhov kev hu mus rau cov kev pabcuam no yog thov hnyav. Qhov no txhais tau hais tias cov nyiaj siv ua haujlwm ntawm kev thauj cov kev thov no ntau dua li lub sijhawm nws siv rau lawv los ntawm kev pabcuam thib peb. Ntawm peb lub rooj zaum sim, lub sijhawm hu rau cov kev pabcuam no yog 100 ms, yog li peb yuav siv cov lej no yav tom ntej.

Peb yuav tsum ua tus tswj REST yooj yim kom tau txais cov lus N kawg nrog tag nrho cov ntaub ntawv tsim nyog. Ntawd yog, peb ntseeg tias cov qauv lus nyob rau hauv pem hauv ntej yuav luag zoo ib yam thiab tag nrho cov ntaub ntawv yuav tsum tau xa. Qhov sib txawv ntawm cov qauv pem hauv ntej yog tias cov ntaub ntawv thiab cov neeg siv yuav tsum tau nthuav tawm hauv daim ntawv decrypted me ntsis thiaj li ua rau lawv txuas:

/** В таком виде отдаются ссылки на сущности для фронта */
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
)

Peb yuav tsum ua raws li cov hauv qab no:

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

UI postfix txhais tau tias DTO qauv rau pem hauv ntej, uas yog, peb yuav tsum ua haujlwm ntawm REST.

Dab tsi yuav xav tsis thoob ntawm no yog tias peb tsis dhau ib qho kev sib tham ID thiab txawm tias tus qauv ChatMessage / ChatMessageUI tsis muaj. Kuv tau ua qhov no txhob txwm ua kom tsis txhob cuam tshuam cov cai ntawm cov piv txwv (cov kev sib tham yog cais, yog li peb tuaj yeem xav tias peb tsuas muaj ib qho xwb).

Philosophical digressionOb chav ChatMessageUI thiab ChatRestApi.getLast txoj kev siv cov npe cov ntaub ntawv hom thaum qhov tseeb nws yog ib qho kev txiav txim. Qhov no tsis zoo nyob rau hauv JDK, yog li tshaj tawm qhov kev txiav txim ntawm cov ntsiab lus ntawm theem interface (tswj qhov kev txiav txim thaum ntxiv thiab tshem tawm) yuav tsis ua haujlwm. Yog li nws tau dhau los ua ib qho kev coj ua los siv Daim Ntawv Teev Npe nyob rau hauv cov xwm txheej uas xav tau kev teeb tsa (tseem muaj LinkedHashSet, tab sis qhov no tsis yog qhov cuam tshuam).
Kev txwv tseem ceeb: Peb yuav xav tias tsis muaj txoj hlua ntev ntev ntawm cov lus teb lossis kev hloov pauv. Ntawd yog, lawv muaj nyob, tab sis lawv qhov ntev tsis tshaj peb cov lus. Tag nrho cov saw ntawm cov lus yuav tsum raug xa mus rau pem hauv ntej.

Txhawm rau tau txais cov ntaub ntawv los ntawm cov kev pabcuam sab nraud muaj cov APIs hauv qab no:

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>
}

Nws tuaj yeem pom tau tias cov kev pabcuam sab nraud pib muab rau kev ua haujlwm batch, thiab hauv ob qho tib si: los ntawm Teeb (tsis khaws cia qhov kev txiav txim ntawm cov ntsiab lus, nrog rau cov yuam sij tshwj xeeb) thiab los ntawm Daim Ntawv Teev Npe (yuav muaj duplicates - qhov kev txiav txim yog khaws cia).

Kev siv yooj yim

Kev siv tsis tau

Thawj qhov kev ua tsis ncaj ncees ntawm peb tus tswj hwm REST yuav zoo li qhov no hauv feem ntau:

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()
    )
}

Txhua yam yog qhov tseeb heev, thiab qhov no yog qhov loj ntxiv.

Peb siv batch ua thiab tau txais cov ntaub ntawv los ntawm ib qho kev pabcuam sab nraud hauv batch. Tab sis ua li cas rau peb cov khoom tsim tau?

Rau txhua cov lus, ib qho hu rau UserRemoteApi yuav ua kom tau txais cov ntaub ntawv ntawm tus sau daim teb thiab ib qho hu rau FileRemoteApi kom tau txais tag nrho cov ntaub ntawv txuas. Qhov ntawd zoo li yog nws. Cia peb hais tias forwardFrom thiab teb rau thaj teb rau ChatMessage tau txais hauv txoj hauv kev uas qhov no tsis tas yuav hu xov tooj tsis tsim nyog. Tab sis tig lawv mus rau hauv ChatMessageUI yuav ua rau rov ua dua, uas yog, hu xov tooj tuaj yeem nce ntxiv. Raws li peb tau sau tseg ua ntej, cia peb xav tias peb tsis muaj ntau qhov zes thiab cov saw hlau txwv rau peb cov lus.

Raws li qhov tshwm sim, peb yuav tau txais los ntawm ob mus rau rau qhov kev hu mus rau cov kev pabcuam sab nraud rau ib qho lus thiab ib qho JPA hu rau tag nrho cov lus. Tag nrho cov xov tooj hu yuav txawv ntawm 2 * N + 1 txog 6 * N + 1. Qhov no ntau npaum li cas hauv cov units? Cia peb hais tias nws yuav siv 20 cov lus los ua ib nplooj ntawv. Yuav kom tau txais lawv, nws yuav siv li ntawm 4 s txog 10 s. Txaus ntshai! Kuv xav khaws cia li ntawm 500 ms. Thiab txij li thaum lawv npau suav ntawm kev ua seamless scrolling nyob rau hauv pem hauv ntej, qhov kev xav tau ntawm qhov kawg no tuaj yeem ua ob npaug.

Tshaj:

  1. Cov cai yog luv luv thiab nws tus kheej cov ntaub ntawv (ib pab neeg txhawb kev npau suav).
  2. Cov cai yooj yim, yog li yuav luag tsis muaj sijhawm los tua koj tus kheej hauv ko taw.
  3. Batch ua tsis zoo li ib yam dab tsi txawv txawv thiab yog organically integrated rau hauv lub logic.
  4. Kev hloov logic yuav ua tau yooj yim thiab yuav nyob hauv zos.

Tshem tawm:

Kev ua haujlwm txaus ntshai vim cov pob ntawv me me heev.

Txoj kev no tuaj yeem pom ntau zaus hauv cov kev pabcuam yooj yim lossis hauv cov qauv. Yog tias qhov ceev ntawm kev hloov pauv yog qhov tseem ceeb, nws tsis yog tsim nyog ua rau lub cev tsis zoo. Nyob rau tib lub sijhawm, rau peb qhov kev pabcuam yooj yim heev, qhov kev ua tau zoo yog qhov txaus ntshai, yog li qhov kev siv tau ntawm txoj hauv kev no yog nqaim heev.

Naive parallel processing

Koj tuaj yeem pib ua txhua cov lus nyob rau hauv tib lub sijhawm - qhov no yuav tso cai rau koj kom tshem tawm cov kab nce hauv lub sijhawm nyob ntawm seb muaj pes tsawg cov lus. Qhov no tsis yog txoj hauv kev zoo tshwj xeeb vim tias nws yuav ua rau muaj qhov siab tshaj plaws ntawm kev pabcuam sab nraud.

Kev siv cov txheej txheem sib luag yog qhov yooj yim heev:

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

Siv cov lus sib txuas ua ke, peb tau txais 300-700 ms lub hom phiaj, uas yog qhov zoo dua nrog kev siv tsis zoo, tab sis tseem tsis ceev txaus.

Nrog rau txoj hauv kev no, kev thov rau userRepository thiab fileRepository yuav raug ua tiav synchronously, uas tsis zoo heev. Txhawm rau txhim kho qhov no, koj yuav tau hloov lub logic hu ntau heev. Piv txwv li, ntawm CompletionStage (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()!!

Nws tuaj yeem pom tau tias qhov pib yooj yim daim npav code tau dhau los ua tsis tshua nkag siab. Qhov no yog vim peb yuav tsum tau cais cov kev hu mus rau cov kev pabcuam sab nraud los ntawm qhov twg cov txiaj ntsig tau siv. Qhov no nyob rau hauv nws tus kheej tsis yog phem. Tab sis kev sib txuas hu tsis zoo heev thiab zoo ib yam li "noodle" reactive.

Yog tias koj siv coroutines, txhua yam yuav zoo dua:

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()
    )
  }

Qhov twg:

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

Raws li kev xav, siv cov txheej txheem sib luag, peb yuav tau txais 200-400 ms, uas twb nyob ze rau peb qhov kev cia siab.

Hmoov tsis zoo, xws li kev sib piv zoo tsis muaj nyob, thiab tus nqi them yog qhov phem heev: tsuas yog ob peb tus neeg siv ua haujlwm tib lub sijhawm, qhov kev thov kev thov yuav poob rau ntawm cov kev pabcuam, uas yuav tsis ua haujlwm rau tib lub sijhawm, yog li peb yuav rov los rau peb tu siab 4 s.

Kuv qhov tshwm sim thaum siv qhov kev pabcuam no yog 1300-1700 ms rau kev ua 20 cov lus. Qhov no yog sai dua nyob rau hauv thawj qhov kev siv, tab sis tseem tsis daws qhov teeb meem.

Lwm txoj kev siv cov lus nug sib npaugYuav ua li cas yog tias cov kev pabcuam thib peb tsis muab kev ua batch? Piv txwv li, koj tuaj yeem nkaum qhov tsis muaj batch ua kev siv hauv cov txheej txheem kev sib txuas:

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())
}

Qhov no ua rau kev txiav txim siab yog tias koj vam tias yuav pom batch ua nyob rau yav tom ntej versions.
Tshaj:

  1. Yooj yim siv cov lus raws li kev sib txuas ua ke.
  2. Zoo scalability.

Txais:

  1. Qhov yuav tsum tau cais cov ntaub ntawv tau los ntawm nws cov kev ua haujlwm thaum ua cov kev thov rau cov kev pabcuam sib txawv nyob rau hauv parallel.
  2. Nce load ntawm cov kev pabcuam thib peb.

Nws tuaj yeem pom tias thaj tsam ntawm kev siv tau yog kwv yees li tib yam li ntawm txoj kev tsis ncaj ncees. Nws ua rau kev txiav txim siab siv txoj kev thov sib npaug yog tias koj xav ua kom tau txais txiaj ntsig ntawm koj qhov kev pabcuam ntau zaus vim yog kev siv tsis zoo ntawm lwm tus. Hauv peb qhov piv txwv, kev ua tau zoo nce 2,5 npaug, tab sis qhov no tsis txaus.

caching

Koj tuaj yeem ua caching hauv tus ntsuj plig ntawm JPA rau cov kev pabcuam sab nraud, uas yog, khaws cov khoom tau txais nyob rau hauv ib qho kev sib tham kom tsis txhob tau txais lawv dua (nrog rau lub sijhawm ua batch). Koj tuaj yeem ua cov caches koj tus kheej, koj tuaj yeem siv Caij Nplooj Ntoos Hlav nrog nws @Cacheable, ntxiv rau koj ib txwm siv lub cache npaj ua tiav zoo li EhCache manually.

Ib qho teeb meem tshwm sim yog tias caches tsuas yog siv tau yog tias lawv muaj hits. Nyob rau hauv peb cov ntaub ntawv, hits ntawm tus sau teb muaj feem ntau (cia peb hais, 50%), tab sis yuav tsis muaj hits ntawm cov ntaub ntawv txhua. Txoj hauv kev no yuav muab qee qhov kev txhim kho, tab sis nws yuav tsis hloov pauv kev ua tau zoo (thiab peb xav tau kev ua tiav).

Intersession (ntev) caches yuav tsum complex invalidation logic. Feem ntau, tom qab koj tau nqis los daws cov teeb meem kev ua tau zoo siv kev sib cuam tshuam caches, qhov zoo dua.

Tshaj:

  1. Siv caching yam tsis hloov code.
  2. Kev tsim khoom ntau ntxiv ntau zaus (hauv qee kis).

Txais:

  1. Muaj peev xwm txo qis kev ua haujlwm yog tias siv tsis raug.
  2. Lub cim xeeb loj nyiaj siv ua haujlwm, tshwj xeeb nrog ntev caches.
  3. Complex invalidation, yuam kev uas yuav ua rau nyuaj-rau-reproduce teeb meem nyob rau hauv runtime.

Feem ntau, caches tsuas yog siv los kho cov teeb meem tsim kho sai sai. Qhov no tsis tau txhais hais tias lawv yuav tsum tsis txhob siv. Txawm li cas los xij, koj yuav tsum ua tib zoo saib xyuas lawv thiab ua ntej ntsuas qhov txiaj ntsig tau txais txiaj ntsig, thiab tsuas yog tom qab ntawd txiav txim siab.

Hauv peb qhov piv txwv, caches yuav muab kev ua haujlwm nce ntawm ib puag ncig 25%. Nyob rau tib lub sijhawm, caches muaj ntau qhov tsis zoo, yog li kuv yuav tsis siv lawv ntawm no.

Cov txiaj ntsim tau los

Yog li, peb tau saib ntawm qhov kev siv tsis zoo ntawm qhov kev pabcuam uas siv cov txheej txheem ua haujlwm, thiab qee txoj hauv kev yooj yim kom nws nrawm dua.

Lub ntsiab kom zoo dua ntawm tag nrho cov no txoj kev yog simplicity, uas muaj ntau yam qab ntxiag txim.

Ib qho teeb meem tshwm sim nrog cov txheej txheem no yog kev ua haujlwm tsis zoo, feem ntau yog vim qhov loj ntawm cov pob ntawv. Yog li, yog tias cov kev daws teeb meem no tsis haum rau koj, ces nws tsim nyog xav txog ntau txoj hauv kev radical.

Muaj ob lub ntsiab lus qhia uas koj tuaj yeem nrhiav kev daws teeb meem:

  • asynchronous ua hauj lwm nrog cov ntaub ntawv (yuav tsum tau ib tug paradigm hloov, yog li tsis tau tham nyob rau hauv no tsab xov xwm);
  • loj ntawm batch thaum tuav synchronous ua.

Kev loj hlob ntawm cov batch yuav txo tau cov xov tooj ntawm sab nraud hu thiab tib lub sij hawm khaws cov code synchronous. Tshooj tom ntej ntawm tsab xov xwm yuav mob siab rau lub ncauj lus no.

Tau qhov twg los: www.hab.com

Ntxiv ib saib