Mga problema sa pagproseso sa batch nga pangutana ug ang ilang mga solusyon (bahin 1)

Mga problema sa pagproseso sa batch nga pangutana ug ang ilang mga solusyon (bahin 1)Hapit tanan nga modernong mga produkto sa software naglangkob sa daghang mga serbisyo. Kasagaran, ang taas nga oras sa pagtubag sa mga channel sa interservice mahimong gigikanan sa mga problema sa pasundayag. Ang sumbanan nga solusyon sa kini nga klase sa problema mao ang pag-pack sa daghang mga hangyo sa interservice sa usa ka pakete, nga gitawag nga batching.

Kung mogamit ka sa pagproseso sa batch, mahimong dili ka malipay sa mga resulta sa mga termino sa pasundayag o katin-aw sa code. Kini nga pamaagi dili ingon kadali sa nagtawag sama sa imong gihunahuna. Alang sa lainlaing katuyoan ug sa lainlaing mga sitwasyon, ang mga solusyon mahimong magkalainlain kaayo. Pinaagi sa paggamit sa piho nga mga pananglitan, akong ipakita ang mga bentaha ug disbentaha sa daghang mga pamaagi.

Proyekto sa demonstrasyon

Alang sa katin-awan, atong tan-awon ang usa ka pananglitan sa usa sa mga serbisyo sa aplikasyon nga akong gitrabaho karon.

Pagpatin-aw sa pagpili sa plataporma alang sa mga pananglitanAng problema sa dili maayo nga pasundayag kay kasagaran ug dili makaapekto sa bisan unsang piho nga mga pinulongan o plataporma. Kini nga artikulo mogamit mga pananglitan sa Spring + Kotlin code aron ipakita ang mga problema ug solusyon. Ang Kotlin parehas nga masabtan (o dili masabtan) sa mga developer sa Java ug C #, dugang pa, ang code mas compact ug masabtan kaysa sa Java. Aron mas dali masabtan ang mga lunsay nga Java developers, likayan nako ang black magic sa Kotlin ug gamiton lang ang white magic (sa espiritu sa Lombok). Adunay pipila ka mga pamaagi sa pagpalapad, apan sila sa tinuud pamilyar sa tanan nga mga programmer sa Java ingon mga static nga pamaagi, mao nga kini usa ka gamay nga asukal nga dili makadaot sa lami sa pinggan.
Adunay serbisyo sa pag-apruba sa dokumento. Adunay usa ka tawo nga naghimo og usa ka dokumento ug nagsumite niini alang sa diskusyon, diin ang mga pag-edit gihimo, ug sa katapusan ang dokumento giuyonan. Ang serbisyo sa pag-apruba mismo wala’y nahibal-an bahin sa mga dokumento: kini usa lamang ka chat sa mga nag-aprobar nga adunay gamay nga dugang nga mga gimbuhaton nga dili namon tagdon dinhi.

Mao nga, adunay mga chat room (katumbas sa mga dokumento) nga adunay gitakda nga set sa mga partisipante sa matag usa niini. Sama sa regular nga mga chat, ang mga mensahe adunay mga teksto ug mga file ug mahimo nga mga tubag o pasulong:

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
)

Ang mga link sa file ug user mga link sa ubang mga domain. Dinhi kita nagpuyo sama niini:

typealias FileReference Long
typealias UserReference Long

Ang datos sa tiggamit gitipigan sa Keycloak ug gikuha pinaagi sa REST. Ingon usab ang alang sa mga file: ang mga file ug metainformation bahin niini nagpuyo sa usa ka lahi nga serbisyo sa pagtipig sa file.

Ang tanan nga mga tawag sa kini nga mga serbisyo mao ang bug-at nga mga hangyo. Kini nagpasabot nga ang overhead sa pagdala niini nga mga hangyo mas dako pa kay sa panahon nga gikinahanglan aron kini maproseso sa usa ka ikatulo nga partido nga serbisyo. Sa among mga bangko sa pagsulay, ang kasagaran nga oras sa pagtawag alang sa ingon nga mga serbisyo mao ang 100 ms, mao nga gamiton namon kini nga mga numero sa umaabot.

Kinahanglan namon nga maghimo usa ka yano nga REST controller aron makadawat sa katapusan nga N nga mga mensahe nga adunay tanan nga kinahanglan nga kasayuran. Sa ato pa, nagtuo kami nga sa frontend ang modelo sa mensahe halos parehas ug ang tanan nga datos kinahanglan ipadala. Ang kalainan tali sa front-end nga modelo mao nga ang file ug ang user kinahanglan nga ipresentar sa gamay nga decrypted nga porma aron mahimo silang mga link:

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

Kinahanglan natong ipatuman ang mosunod:

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

Ang postfix sa UI nagpasabut nga mga modelo sa DTO alang sa frontend, nga mao, kung unsa ang kinahanglan naton i-serve pinaagi sa REST.

Ang makapakurat dinhi mao nga wala kami nagpasa sa bisan unsang chat ID ug bisan ang modelo sa ChatMessage/ChatMessageUI wala. Gituyo nako kini aron dili mabutang ang kodigo sa mga pananglitan (ang mga chat gilain, aron atong mahunahuna nga kita adunay usa lamang).

Pilosopikal nga pagtipasAng ChatMessageUI nga klase ug ang ChatRestApi.getLast nga pamaagi naggamit sa List data type kung sa pagkatinuod kini usa ka gimando nga Set. Kini dili maayo sa JDK, mao nga ang pagdeklarar sa han-ay sa mga elemento sa lebel sa interface (pagpreserbar sa han-ay sa pagdugang ug pagtangtang) dili molihok. Mao nga nahimo na nga naandan nga praktis ang paggamit sa usa ka Lista sa mga kaso diin gikinahanglan ang usa ka gimando nga Set (adunay usab LinkedHashSet, apan dili kini usa ka interface).
Importante nga limitasyon: Atong hunahunaon nga walay taas nga mga kutay sa mga tubag o pagbalhin. Sa ato pa, anaa sila, apan ang ilang gitas-on dili molapas sa tulo ka mga mensahe. Ang tibuok kadena sa mga mensahe kinahanglang ipadala ngadto sa frontend.

Aron makadawat sa datos gikan sa mga serbisyo sa gawas adunay mga mosunod nga mga API:

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

Makita nga ang mga eksternal nga serbisyo sa sinugdan naghatag alang sa pagproseso sa batch, ug sa duha nga mga bersyon: pinaagi sa Set (nga wala gipreserbar ang han-ay sa mga elemento, nga adunay talagsaon nga mga yawe) ug pinaagi sa List (mahimo nga adunay mga duplicate - ang order gipreserbar).

Yano nga mga pagpatuman

Wala'y pulos nga pagpatuman

Ang una nga wala’y hinungdan nga pagpatuman sa among REST controller mahimong sama niini sa kadaghanan nga mga kaso:

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

Ang tanan klaro kaayo, ug kini usa ka dako nga dugang.

Gigamit namo ang pagproseso sa batch ug nakadawat og data gikan sa usa ka eksternal nga serbisyo sa mga batch. Apan unsay mahitabo sa atong pagka-produktibo?

Alang sa matag mensahe, usa ka tawag sa UserRemoteApi ang himuon aron makakuha og datos sa natad sa tagsulat ug usa ka tawag sa FileRemoteApi aron makuha ang tanan nga gilakip nga mga file. Murag mao na. Ingnon ta nga ang forwardFrom ug replyTo fields para sa ChatMessage nakuha sa paagi nga wala kini magkinahanglan ug wala kinahanglana nga mga tawag. Apan ang paghimo niini sa ChatMessageUI mosangput sa pagbalik-balik, nga mao, ang mga call counter mahimong madugangan pag-ayo. Sama sa atong namatikdan sa sayo pa, atong hunahunaon nga kita walay daghang mga salag ug ang kadena limitado sa tulo ka mga mensahe.

Ingon usa ka sangputanan, makadawat kami gikan sa duha hangtod unom ka tawag sa mga eksternal nga serbisyo matag mensahe ug usa ka tawag sa JPA alang sa tibuuk nga pakete sa mga mensahe. Ang kinatibuk-ang gidaghanon sa mga tawag magkalahi gikan sa 2*N+1 ngadto sa 6*N+1. Pila kini sa tinuod nga mga yunit? Ingnon ta nga gikinahanglan ang 20 ka mensahe aron ma-render ang usa ka panid. Aron madawat kini, magkinahanglan kini gikan sa 4 s hangtod 10 s. Makalilisang! Gusto nako nga itago kini sulod sa 500 ms. Ug tungod kay nagdamgo sila sa paghimo sa hapsay nga pag-scroll sa frontend, ang mga kinahanglanon sa pasundayag alang niini nga katapusan nga punto mahimong madoble.

Mga Pro:

  1. Ang kodigo mubo ug self-documenting (damgo sa support team).
  2. Ang code mao ang yano, mao nga halos walay mga oportunidad sa pagpusil sa imong kaugalingon sa tiil.
  3. Ang pagproseso sa batch dili ingon usa ka butang nga langyaw ug gisagol sa organiko sa lohika.
  4. Ang mga pagbag-o sa lohika mahimo nga dali ug mahimong lokal.

Minus:

Makalilisang nga pasundayag tungod sa gamay kaayo nga mga pakete.

Kini nga pamaagi makita kanunay sa yano nga mga serbisyo o sa mga prototype. Kung ang katulin sa paghimo sa mga pagbag-o hinungdanon, dili gyud angay nga komplikado ang sistema. Sa parehas nga oras, alang sa among yano kaayo nga serbisyo ang pasundayag makalilisang, mao nga ang kasangkaran sa pagpadapat niini nga pamaagi hiktin kaayo.

Naive parallel nga pagproseso

Mahimo nimong sugdan ang pagproseso sa tanan nga mga mensahe nga managsama - kini magtugot kanimo sa pagtangtang sa linear nga pagtaas sa oras depende sa gidaghanon sa mga mensahe. Dili kini usa ka maayo nga agianan tungod kay kini moresulta sa usa ka dako nga peak load sa gawas nga serbisyo.

Ang pagpatuman sa parallel nga pagproseso yano ra kaayo:

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

Gamit ang parallel nga pagproseso sa mensahe, makuha nato ang 300–700 ms nga labing maayo, nga mas maayo kay sa walay pulos nga pagpatuman, apan dili pa igo nga paspas.

Uban niini nga pamaagi, ang mga hangyo sa userRepository ug fileRepository ipatuman nga dungan, nga dili kaayo episyente. Aron ayohon kini, kinahanglan nimo nga usbon ang lohika sa pagtawag. Pananglitan, pinaagi sa 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()!!

Makita nga ang una nga yano nga mapping code nahimong dili kaayo masabtan. Kini tungod kay kinahanglan namon nga ibulag ang mga tawag sa mga serbisyo sa gawas kung diin gigamit ang mga resulta. Kini sa iyang kaugalingon dili daotan. Apan ang paghiusa sa mga tawag dili kaayo elegante ug susama sa usa ka tipikal nga reaktibo nga "noodle".

Kung mogamit ka og mga coroutine, ang tanan tan-awon nga mas disente:

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

Asa:

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

Sa teoriya, gamit ang ingon nga parallel nga pagproseso, makakuha kita og 200-400 ms, nga duol na sa atong mga gilauman.

Ikasubo, ang ingon nga maayo nga parallelization wala maglungtad, ug ang presyo nga ibayad labi ka mabangis: nga adunay pipila ra nga mga tiggamit nga nagtrabaho sa parehas nga oras, usa ka barrage sa mga hangyo ang mahulog sa mga serbisyo, nga dili maproseso nga parehas, mao nga kami mubalik sa among sad 4 s.

Ang akong resulta kung mogamit sa ingon nga serbisyo mao ang 1300-1700 ms alang sa pagproseso sa 20 nga mga mensahe. Kini mas paspas kay sa unang pagpatuman, apan wala gihapon makasulbad sa problema.

Alternatibong paggamit sa parallel nga mga pangutanaUnsa kaha kung ang mga serbisyo sa ikatulo nga partido wala maghatag pagproseso sa batch? Pananglitan, mahimo nimong itago ang kakulang sa pagpatuman sa pagproseso sa batch sulod sa mga pamaagi sa interface:

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

Makataronganon kini kung naglaum ka nga makita ang pagproseso sa batch sa umaabot nga mga bersyon.
Mga Pro:

  1. Sayon nga ipatuman ang parallel nga pagproseso nga nakabase sa mensahe.
  2. Maayong scalability.

Kahinumduman:

  1. Ang panginahanglan sa pagbulag sa data acquisition gikan sa pagproseso niini sa diha nga pagproseso sa mga hangyo ngadto sa lain-laing mga serbisyo sa parallel.
  2. Nadugangan nga load sa mga serbisyo sa ikatulo nga partido.

Makit-an nga ang kasangkaran sa pagpadapat hapit parehas sa naive nga pamaagi. Makataronganon nga gamiton ang parallel request method kung gusto nimo nga dugangan ang performance sa imong serbisyo sa makadaghang higayon tungod sa walay kaluoy nga pagpahimulos sa uban. Sa among panig-ingnan, ang pasundayag misaka sa 2,5 ka beses, apan kini klaro nga dili igo.

pag-cache

Mahimo nimong buhaton ang pag-cache sa espiritu sa JPA alang sa mga serbisyo sa gawas, nga mao, pagtago sa nadawat nga mga butang sa sulod sa usa ka sesyon aron dili kini madawat pag-usab (lakip na sa pagproseso sa batch). Mahimo nimo ang ingon nga mga cache sa imong kaugalingon, mahimo nimong gamiton ang Spring gamit ang @Cacheable, ug mahimo nimo kanunay nga magamit ang usa ka andam nga cache sama sa EhCache nga mano-mano.

Usa ka kasagarang problema mao nga ang mga cache mapuslanon lamang kung kini adunay mga hit. Sa among kaso, ang mga hit sa natad sa tagsulat lagmit kaayo ( ingnon ta, 50%), apan wala’y mga hit sa mga file. Kini nga pamaagi maghatag pipila ka mga pag-uswag, apan dili kini radikal nga magbag-o sa pasundayag (ug kinahanglan namon ang usa ka kauswagan).

Ang intersession (taas) nga mga cache nanginahanglan komplikado nga lohika sa pagka-invalidation. Sa kinatibuk-an, kung kanus-a ka makasulbad sa mga problema sa pasundayag gamit ang mga intersession cache, mas maayo.

Mga Pro:

  1. Ipatuman ang caching nga walay pag-usab sa code.
  2. Dugang nga produktibo sa daghang mga higayon (sa pipila ka mga kaso).

Kahinumduman:

  1. Posibilidad sa pagkunhod sa performance kung gigamit sa dili husto.
  2. Dako nga memorya sa ibabaw, labi na sa taas nga mga cache.
  3. Komplikado nga pagka-invalidation, mga kasaypanan diin mosangpot sa lisud nga pag-reproduce nga mga problema sa runtime.

Kasagaran, ang mga cache gigamit lamang aron dali nga masulbad ang mga problema sa disenyo. Kini wala magpasabot nga sila dili kinahanglan nga gamiton. Bisan pa, kinahanglan nimo nga tagdon sila kanunay nga mabinantayon ug susihon una ang sangputanan nga nakuha sa pasundayag, ug pagkahuman maghimo usa ka desisyon.

Sa among pananglitan, ang mga cache maghatag usa ka pagtaas sa pasundayag sa hapit 25%. Sa parehas nga oras, ang mga cache adunay daghang mga kakulangan, mao nga dili nako kini gamiton dinhi.

Mga resulta

Mao nga, among gitan-aw ang usa ka walay pulos nga pagpatuman sa usa ka serbisyo nga naggamit sa pagproseso sa batch, ug pipila ka yano nga mga paagi aron mapadali kini.

Ang nag-unang bentaha sa tanan niini nga mga pamaagi mao ang kayano, nga gikan diin adunay daghang makapahimuot nga mga sangputanan.

Usa ka kasagarang problema sa kini nga mga pamaagi mao ang dili maayo nga pasundayag, labi na tungod sa kadako sa mga pakete. Busa, kung kini nga mga solusyon dili angay kanimo, nan angay nga hunahunaon ang labi ka radikal nga mga pamaagi.

Adunay duha ka nag-unang direksyon diin mahimo nimong pangitaon ang mga solusyon:

  • asynchronous nga buhat uban sa data (nagkinahanglan sa usa ka paradigm shift, mao nga wala hisgoti niini nga artikulo);
  • pagpadako sa mga batch samtang nagpadayon sa dungan nga pagproseso.

Ang pagpadako sa mga batch makapakunhod pag-ayo sa gidaghanon sa mga eksternal nga tawag ug sa samang higayon ipadayon ang code nga magkadungan. Ang sunod nga bahin sa artikulo ipahinungod niini nga topiko.

Source: www.habr.com

Idugang sa usa ka comment