.author{grid-column:1;grid-row:3;align-content:flex-start}.Metadata_root__oCstk>.description,.Metadata_root__oCstk>.pills-list,.Metadata_root__oCstk>.stats,.Metadata_root__oCstk>.title{grid-column:1/span 2}.Metadata_root__oCstk>.title{margin-bottom:8px}.Metadata_root__oCstk>.stats{margin-bottom:12px}.Metadata_root__oCstk>.description{margin-top:12px}.Metadata_root__oCstk>.pills-list{margin-block-start:20px;margin-block-end:18px}.Metadata_root__oCstk>.actions{display:none}@media screen and (max-width:928px){.Metadata_root__oCstk{margin-block:8px 20px}.Metadata_root__oCstk>.stats{margin-bottom:16px}.Metadata_root__oCstk>.description{margin-top:0}.Metadata_root__oCstk>.author{grid-column:2;grid-row:5;margin-block-start:12px;margin-block-end:6px;justify-content:end}.Metadata_root__oCstk>.actions{display:flex;margin-top:16px;grid-column:1;grid-row:5}.Metadata_root__oCstk>.pills-list{margin-block-start:16px;margin-block-end:0}}.Metadata_root__oCstk .metadata-recs{grid-column:1/-1}.Heading_heading__3MAvZ{color:var(--blue-gray-900)}.Heading_h1__3k7S2{font-size:32px;font-weight:700}.Heading_h2__f9yvs{font-size:28px;font-weight:600}.Heading_h3__f1djd{font-size:24px}.Heading_h4__7tfLE{font-size:20px}.Heading_h5__jVM0l{font-size:16px;font-weight:400}.Heading_h6__uUTrd{font-size:14px;font-weight:400}.Title_root__svkHQ{color:var(--blue-gray-900);font-size:clamp(26px,1vw + 1rem,28px);font-weight:600;line-height:1.25;min-width:0;word-break:break-word}.Stats_root__p_BoZ{flex-wrap:wrap;display:flex;align-items:center;-moz-column-gap:6px;column-gap:6px;color:var(--blue-gray-600);white-space:nowrap;font-size:16px}.Stats_leftContent__588PR,.Stats_rightContent__8d0AF{display:flex;gap:6px}.Stats_root__p_BoZ span{font-size:16px}.Stats_root__p_BoZ .Stats_aiTag__zTzW8{margin-left:10px}@media screen and (max-width:928px){.Stats_root__p_BoZ span{line-height:1.5}.Stats_root__p_BoZ.Stats_extendedMetadata__wb62p .Stats_leftContent__588PR{width:100%}.Stats_root__p_BoZ .Stats_aiTag__zTzW8{margin:8px 0 0}.Stats_root__p_BoZ.Stats_extendedMetadata__wb62p .Stats_formatTypesBullet__xDv0L{display:none}}.Likes_root__WVQ1_{cursor:pointer;transition:color .2s ease-in-out;border-radius:4px}.Likes_root__WVQ1_:hover{color:var(--blue-gray-700)}.Tooltip_root__7FS0Y{background:var(--midnight-green-dark);border-radius:4px;box-shadow:0 .5px 5px rgba(0,0,0,.04),0 4px 11px rgba(0,0,0,.2);color:var(--white);font-weight:400;font-size:12px;line-height:15px;padding:6px 8px;opacity:0;visibility:hidden;animation:Tooltip_show__qVG5k .2s ease-in-out forwards;z-index:var(--popup-index)}.Tooltip_triggerWrapper___S2HG{flex-shrink:0;position:relative;align-items:center;justify-content:center}@keyframes Tooltip_show__qVG5k{to{opacity:1;visibility:visible}}.Tooltip_large__J4Fvl{padding:16px;display:flex;flex-direction:column;background:#fff;color:var(--black)}.AITag_tag__Xx37c{padding:0 12px;height:25px;border-radius:16px;background-color:#f0f2f9;color:#16171b;display:inline-flex;gap:8px;font-size:14px;line-height:1.5;font-weight:600}.AITag_tooltipContent__7JZR_{width:332px}.Author_root___6Bx5{--link-color:var(--blue-gray-800);position:relative;display:flex;align-items:center;gap:8px}.Author_link___lVxw{z-index:1;color:var(--link-color);font-weight:600;display:block}.Author_link___lVxw:before{content:"";position:absolute;inset:0}.Author_follow__Lw4TS{z-index:1}@media screen and (max-width:928px){.Author_link___lVxw:hover{color:var(--blue-gray-800)}}.Avatar_root__GNWHY{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;background-color:var(--white);color:var(--blue-gray-300);border-radius:50%;font-size:16px;font-weight:600;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;overflow:hidden}.Avatar_initials__EJfVt{color:var(--white);transition:background-color .2s ease-in-out}.Avatar_initials__EJfVt,.Avatar_initials__EJfVt:hover{background-color:var(--blue-gray-600)}.Avatar_image__Bbtll{width:100%;height:100%;-o-object-fit:cover;object-fit:cover}.FollowButton_root__FxpBi{display:inline-flex;background-color:transparent;border:1px solid transparent;border-radius:4px;font-size:12px;padding:1px 6px;transition:background-color .2s ease-in-out,border-color .2s ease-in-out;cursor:pointer}.FollowButton_following__xKCww{border-color:#bf5905;color:#bf5905}.FollowButton_following__xKCww:hover{background-color:#ffead7;border-color:rgba(191,89,5,.5)}.FollowButton_follow__d_6u5{border-color:var(--celadon-blue-dark);color:var(--celadon-blue-dark)}.FollowButton_follow__d_6u5:hover{background-color:#eaf7ff;border-color:rgba(2,126,176,.5)}@media screen and (max-width:928px){.FollowButton_root__FxpBi{display:none}}.Description_root__kt4uq{--line-height:26px;position:relative}.Description_root__kt4uq.Description_clamped__PaV_1{padding-bottom:25px}.Description_root__kt4uq.Description_clamped__PaV_1 .Description_wrapper__hYE9_{mask-image:linear-gradient(to bottom,var(--white),transparent);-webkit-mask-image:linear-gradient(to bottom,var(--white),transparent)}.Description_wrapper__hYE9_{min-height:var(--line-height);display:-webkit-box;overflow:hidden;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-line-clamp:2}.Description_noClamp__1z7c5,.Description_wrapper__hYE9_.Description_expanded__lRamt{-webkit-line-clamp:unset;-webkit-mask-image:none;mask-image:none}.Description_wrapper__hYE9_.Description_expanded__lRamt{height:auto}.Description_wrapper__hYE9_ p{color:var(--blue-gray-600);font-size:18px;line-height:var(--line-height);white-space:pre-wrap;word-break:break-word}.Description_more__ChrRK{position:absolute;padding:0;bottom:0;height:26px}.Description_less__BvWbY{padding:0}.Description_hidden__a9QZJ{display:none}@media screen and (max-width:928px){.Description_more__ChrRK{right:0;background-color:#fff}.Description_root__kt4uq.Description_clamped__PaV_1{padding-bottom:0}.Description_less__BvWbY,.Description_more__ChrRK{height:var(--line-height)}}.PillsList_root__2EydN{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.Pill_root__IqOYH{--bg:color-mix(in srgb,var(--celadon-blue-dark),90% transparent);--color:var(--blue-gray-800);height:40px;display:inline-flex;align-items:center;gap:6px;background-color:var(--bg);border-radius:100vmax;color:var(--color);font-size:16px;font-weight:600;padding-inline:16px;transition:color .2s ease-in-out,background-color .2s ease-in-out,filter .2s ease-in-out;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;text-decoration:none;white-space:nowrap}.Pill_root__IqOYH:not(.Pill_selected__VPtHm):hover{filter:brightness(.6)}.Pill_root__IqOYH.Pill_selected__VPtHm{--bg:var(--blue-gray-900);--color:var(--white)}@media screen and (max-width:520px){.Pill_root__IqOYH{height:28px;padding-inline:12px;gap:4px;font-size:12px}.Pill_icon__xE_Cg{--size:18px!important}}.Actions_root__00yIC{display:flex;align-items:baseline;gap:6px}.ReadingModeTooltip_root__laf5h{width:277px}.ToggleButtonGroup_root__vtGE_{display:inline-flex;gap:4px;padding:5px 8px;border-radius:4px;background-color:var(--white);border:1px solid var(--blue-gray-200)}.ToggleButton_root__jGx6U{border-radius:4px;background-color:var(--white);color:var(--blue-gray-700);border:none;width:44px;height:34px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.ToggleButton_selected__51g6d{background-color:var(--blue-gray-200)}.SaveLoggedIn_icon__lk74r{color:var(--blue-gray-700)}.PopoverMenuContent_root__MsRtR{background:var(--white);box-shadow:0 .5px 5px rgba(0,0,0,.04),0 4px 11px rgba(0,0,0,.2);border-radius:4px;opacity:0;visibility:hidden;transition:opacity .2s ease-in-out,visibility .2s ease-in-out;z-index:1000;overflow-y:auto}.PopoverMenuContent_root__MsRtR.PopoverMenuContent_visible__O86I_{opacity:1;visibility:visible;transition-delay:0s}.PopoverMenuItem_item__iazpP{width:100%;display:flex;align-items:center;background-color:transparent;color:inherit;cursor:pointer;font-size:inherit;line-height:24px;padding:12px 16px;white-space:nowrap}.PopoverMenuItem_item__iazpP.PopoverMenuItem_highlight__inbqK,.PopoverMenuItem_item__iazpP:hover{background-color:var(--blue-gray-100)}.PopoverMenuSeparator_separator__UpSGw{width:calc(100% - 32px);height:1px;background-color:var(--blue-gray-300);margin:8px 16px}.SavePopover_popover__mZhIY{width:290px;max-height:220px;color:var(--blue-gray-800);font-weight:400;padding:6px 8px;font-size:12px;line-height:15px;overflow:hidden}.SavePopover_saveToNewList__MVBSu{display:flex;align-items:center;gap:6px;color:var(--midnight-green-dark);font-weight:600;font-size:18px;line-height:24px;padding:10px;width:100%}.SavePopover_addIcon__aJLJ3{color:var(--blue-gray-800)}.SavePopover_horizontalSeparator__bsI6Y{border:none;border-bottom:1px solid var(--blue-gray-300);margin:8px 0}.SavePopover_listsContainer__tGGtp{height:100%;max-height:144px;display:flex;flex-direction:column;align-items:center;overflow-y:auto}.SavePopover_saveToList__Fsfl9{color:var(--blue-gray-800);font-weight:400;font-size:16px;justify-content:space-between;line-height:24px;padding:12px 14px 12px 16px;width:100%}.SavePopover_saveToList__Fsfl9 span:not(.SavePopover_clampLines__Blxtm){display:flex;flex-direction:row;gap:35px;justify-content:space-between}.SavePopover_saveToList__Fsfl9 span.SavePopover_clampLines__Blxtm{display:inline-block;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap;width:180px}.SavePopover_listIcon__OVsXv{color:var(--blue-gray-900)}.SavePopover_noSavedLists__0Mh_H{color:var(--blue-gray-600);font-weight:400;font-size:16px;line-height:24px;padding:12px 16px;text-align:center}.SaveOptionsDrawer_drawerTrigger__Gb7nK{box-shadow:none;border:none;border-radius:4px;position:relative;z-index:7}.SaveOptionsDrawer_drawerTrigger__Gb7nK:active,.SaveOptionsDrawer_drawerTrigger__Gb7nK:active:focus,.SaveOptionsDrawer_drawerTrigger__Gb7nK:hover{border:none;box-shadow:none;background-color:transparent}.SaveOptionsDrawer_drawerTrigger__Gb7nK:focus{background:transparent}.SaveOptionsDrawerContent_drawerContent__J5JTL{margin:16px}.SaveOptionsDrawerContent_separator__UW5Rz{margin:8px 0}.SaveOptionsDrawerContent_itemsContainer__tCXw5{display:flex;flex-direction:column;margin-top:0;max-height:144px;overflow-y:auto}.SaveOptionsDrawerContent_drawerActionSecondary__7cq8j{font-weight:400;line-height:24px;padding:0 8px;margin:8px 0;width:100%}.SaveOptionsDrawerContent_drawerActionSecondary__7cq8j span{color:var(--blue-gray-800);justify-content:space-between;gap:30px}.SaveOptionsDrawerContent_drawerActionPrimary____QuR span{gap:14px}.SaveOptionsDrawerContent_drawerActionPrimary____QuR{color:var(--blue-gray-800);display:flex;font-weight:400;line-height:24px;align-items:center;justify-content:flex-start;padding:8px 4px;margin:14px 0}.SaveOptionsDrawerContent_drawerActionPrimary____QuR.SaveOptionsDrawerContent_newListButton__VzhfD{font-size:16px;font-weight:600}.SaveOptionsDrawerContent_addIcon__Wb2cq{color:var(--blue-gray-800)}.SaveOptionsDrawerContent_drawerActionSecondary__7cq8j span.SaveOptionsDrawerContent_clampLines__zfkfI{display:inline-block;overflow:hidden;text-align:left;text-overflow:ellipsis;white-space:nowrap;width:calc(100vw - 115px)}.SaveOptionsDrawerContent_listIcon__5dcfC{color:var(--blue-gray-900)}.SaveOptionsDrawerContent_noSavedLists__cpUBY{color:var(--blue-gray-600);font-weight:400;font-size:16px;line-height:24px;padding:12px 16px;text-align:center}.Separator_root__70Ime{--orientationMargin:0;background-color:var(--blue-gray-200);flex-shrink:0}.Separator_horizontal__czVEa{width:calc(100% - var(--orientationMargin) * 2);height:1px}.Separator_vertical__JYCCK{width:1px;height:calc(100% - var(--orientationMargin) * 2)}.SaveLoggedOut_icon__ny9X2{color:var(--blue-gray-700)}.Dropdown_root__Z78h8{display:inline-block;position:relative;color:inherit}.DropdownTrigger_trigger__SzsBj{display:flex;align-items:center;justify-content:center;background:transparent;border:none;font-size:inherit;padding:0;margin:0;cursor:pointer}.DropdownTrigger_trigger__SzsBj:active,.DropdownTrigger_trigger__SzsBj:focus,.DropdownTrigger_trigger__SzsBj:hover{background:transparent}.DropdownContent_content__3daFs{position:absolute;display:flex;flex-direction:column;align-items:flex-start;padding:8px 0;background-color:#fff;box-shadow:0 .5px 5px rgba(0,0,0,.039),0 3.75px 11px rgba(0,0,0,.19);border-radius:4px;color:var(--blue-gray-800);opacity:0;visibility:hidden;transition:transform .15s,opacity .15s,visibility 0s linear .15s;transform:scale(.95);z-index:var(--dropdown-index)}.DropdownContent_bottom-left__gioqM{top:calc(100% + 6px);left:0;transform-origin:top left}.DropdownContent_bottom-right__QJ94h{top:calc(100% + 6px);right:0;transform-origin:top right}.DropdownContent_top-left__O3Ryp{bottom:calc(100% + 6px);left:0;transform-origin:bottom left}.DropdownContent_top-right___Qe45{bottom:calc(100% + 6px);right:0;transform-origin:bottom right}.DropdownContent_content__3daFs.DropdownContent_open__6VoiP{visibility:visible;opacity:1;transform:none;transition-delay:0s}.DropdownItem_item__Sv0GT{width:100%;display:flex;align-items:center;background-color:transparent;color:inherit;cursor:pointer;font-size:inherit;line-height:24px;padding:12px 16px;white-space:nowrap}.DropdownItem_highlight__jO3zg,.DropdownItem_item__Sv0GT:hover{background-color:var(--blue-gray-100)}.DropdownSeparator_separator__EN82n{width:calc(100% - 32px);height:1px;background-color:var(--blue-gray-300);margin:8px 16px}.MoreDropdownButton_moreOptionsDropdown__GK_Uw{display:flex;align-items:center;justify-content:center;width:32px;height:32px}.MoreDropdownButton_item__t4HmI{gap:12px}.MoreDropdownButton_moreOptionsIcon__TpJLA{color:var(--blue-gray-700)}.MoreDropdownButton_icon__DxfY4{color:var(--blue-gray-800)}.MoreDropdownButton_tooltip__az4od{white-space:nowrap}.MetadataToolbar_root__c03ao{--shadow-opacity:0;display:grid;height:var(--metadata-toolbar-height);background-color:var(--white);z-index:var(--header-index)}.MetadataToolbar_wrapper__r7XEc{position:relative;display:grid;grid-template-columns:minmax(0,1fr) max-content 1fr;align-items:center;padding-inline-end:20px;padding-block:16px}.MetadataToolbar_underline__QQn0C{grid-column:1/-1;grid-row:2;height:1px;position:absolute;bottom:0;right:-20px;left:-30vw;background-color:var(--blue-gray-200);box-shadow:0 2px 4px 0 rgba(0 0 0/var(--shadow-opacity));animation:MetadataToolbar_reveal-shadow__5yBxP linear both;animation-timeline:scroll(block);animation-range:150px 450px}.MetadataToolbar_verticalMode__Bh759{position:sticky}.MetadataToolbar_title__jfTWv{font-size:18px;font-weight:600}.MetadataToolbar_actions__FB33C{width:-moz-max-content;width:max-content;justify-self:flex-end;display:flex;grid-column-end:-1}.MetadataToolbar_pageNumber__i6Bhj{display:inline-flex;align-items:center;margin-inline:1em;height:44px}.MetadataToolbar_isInReadingModeVariant__XMDcr{align-items:flex-start}.MetadataToolbar_isInReadingModeVariant__XMDcr .MetadataToolbar_downloadButton__ncS7o>button{padding:0 15px}@media screen and (max-width:928px){.MetadataToolbar_root__c03ao{display:none}}@media screen and (min-width:929px) and (max-width:1249px){.MetadataToolbar_isInReadingModeVariant__XMDcr .MetadataToolbar_pageNumber__i6Bhj{display:none}}@keyframes MetadataToolbar_reveal-shadow__5yBxP{to{--shadow-opacity:0.122}}.DownloadButton_root__adY00{margin-left:auto;display:inline-grid;gap:6px;justify-items:center;flex-shrink:0}.DownloadButton_savedStyling__k18od{font-weight:600;font-size:18px}.DownloadMultipleFormatDrawer_root__CWFxX{width:100%;padding:0 24px}.DownloadMultipleFormatDrawer_drawerHeading__8LnFw{margin:16px 0}.DownloadMultipleFormatDrawer_drawerContent__y815X{width:100%;padding:24px 0}.DownloadMultipleFormatDrawer_drawerRadioButtons__I_lQ4 label{margin-bottom:20px}.DownloadMultipleFormatDrawer_drawerRadioButtons__I_lQ4{margin-bottom:4px}.Fieldset_root__L2NQU{display:grid;padding:0;border:0}.Fieldset_root__L2NQU legend{display:none}.DownloadMultipleFormatPopover_popoverContent__IJudF{min-width:185px}.FadeInOut_root__v7Efq{position:relative;min-width:0;background-color:var(--snow-gray);padding:20px}.FadeInOut_root__v7Efq.FadeInOut_isInfographic__PdX2K{background-color:unset;padding-inline-start:0;padding-inline-end:20px}@media (max-width:928px){.FadeInOut_root__v7Efq,.FadeInOut_root__v7Efq.FadeInOut_isInfographic__PdX2K{background-color:unset;padding-inline:2px}}.FadeInOut_in__9dYWz{animation:FadeInOut_fadeIn__14JD0 .5s}.FadeInOut_out___eDZk{animation:FadeInOut_fadeOut__ILQaD .4s}@keyframes FadeInOut_fadeIn__14JD0{0%{opacity:0}to{opacity:1}}@keyframes FadeInOut_fadeOut__ILQaD{0%{opacity:1}to{opacity:0}}.VerticalSlideOverlayed_root__9Thd4{position:relative}.VerticalSlideOverlayed_root__9Thd4 .vertical-slide-image{position:absolute;inset:0}.VerticalSlideOverlayed_active___p5f2 .vertical-slide-image{opacity:.4}.VerticalSlideOverlayed_active___p5f2{background-color:#000}.VerticalSlideImage_image__VtE4p{--ease:cubic-bezier(0.2,0,0,1);width:100%;height:100%;-o-object-fit:contain;object-fit:contain;box-shadow:0 0 0 1px var(--blue-gray-200);border-radius:8px;transition:opacity .3s var(--ease);opacity:0;overflow:clip}.VerticalSlideImage_loading__3pG2z{background-color:var(--blue-gray-600);animation-name:VerticalSlideImage_pulse__OPBSn;animation-direction:alternate;animation-duration:1s;animation-iteration-count:infinite;animation-timing-function:ease-in-out}.VerticalSlideImage_loaded__Q7FLb{opacity:1}@keyframes VerticalSlideImage_pulse__OPBSn{0%{opacity:.09}to{opacity:.15}}.SlideScrollDetector_root__AIK38{position:relative;height:100%;width:100%;pointer-events:none}.SlideScrollDetector_detector__rhkyk{position:absolute;top:0;left:0;height:1px;width:100%}.SlideScrollDetector_detector1__NmF8s{top:25%}.SlideScrollDetector_detector2__32XWr{top:75%}.SlideActions_root__fB9Q4{position:absolute;right:0;top:0;display:grid;grid-template-columns:40px;grid-template-rows:40px 40px;grid-column-gap:12px;-moz-column-gap:12px;column-gap:12px;grid-row-gap:12px;row-gap:12px;padding:12px;justify-content:flex-end;align-items:center;text-align:end}.SlideActions_active__aD_e1{grid-template-columns:max-content 40px}.SlideActions_button__o8UXk{background-color:var(--blue-gray-100);color:var(--blue-gray-700)}.VerticalSlide_infographic__ij1FA,.VerticalSlide_root__jU_9r{position:relative}.VerticalSlide_root__jU_9r .vertical-slide-image{position:absolute;inset:0}.VerticalSlide_infographic__ij1FA .vertical-slide-image{position:unset}.VerticalPlayer_root__K8_YS{position:relative;display:grid;grid-template-columns:minmax(0,1fr);grid-gap:24px;gap:24px}@media screen and (max-width:928px){.VerticalPlayer_root__K8_YS{gap:8px}}.FreestarVideoAd_root__KDWgl{min-width:0;flex-shrink:0;aspect-ratio:16/9}.VerticalInterstitialAdWrapper_root__LxQh8{container-type:inline-size;position:relative;display:grid;min-height:280px;overflow-x:clip}.VerticalInterstitialAdWrapper_root__LxQh8:has(.interstitial-ad-container.has-fetched):not(:has(.interstitial-ad-container.has-fetched .freestar-ad-container:not(.unfilled))){display:none}.VerticalInterstitialAdWrapper_root__LxQh8:has(div.interstitial-ad-container.has-fetched div.freestar-ad-container.filled div),.VerticalInterstitialAdWrapper_root__LxQh8:has(div.interstitial-ad-container.has-fetched div.freestar-ad-container.unfilled iframe){display:block!important}.VerticalInterstitial_root__Dunl7{display:none}@media (max-width:520px){.VerticalInterstitial_root__Dunl7:not(.VerticalInterstitial_inVariant__xB9lL){display:block}}@container (max-width: 520px){.VerticalInterstitial_root__Dunl7.VerticalInterstitial_inVariant__xB9lL{display:block}}.FreestarAdContainer_root__qPPC_{position:relative;display:grid;place-content:center}.FreestarAdContainer_root__qPPC_.FreestarAdContainer_withFallback__A4lgm{aspect-ratio:var(--fallback-aspect-ratio)}.FreestarAdContainer_fallback__WreT9{position:absolute;inset:0;grid-template-columns:unset;place-content:center}.AdFallback_root__uAXsl{display:grid;justify-items:center;grid-template-columns:1fr;z-index:0}.MultipleIncontentSmall_root__x58Hs{display:none;grid-template-columns:repeat(auto-fit,minmax(max-content,300px));place-content:center;gap:16px}@media (min-width:521px){.MultipleIncontentSmall_root__x58Hs:not(.MultipleIncontentSmall_inVariant__uf1S8){display:grid}}@container (max-width: 616px){.MultipleIncontentSmall_root__x58Hs:not(.MultipleIncontentSmall_inVariant__uf1S8) .freestar-ad-container:nth-of-type(2){display:none}}@container (min-width: 616px) and (max-width: 688px){.MultipleIncontentSmall_root__x58Hs.MultipleIncontentSmall_inVariant__uf1S8{display:grid}}.MultipleIncontentWide_root__4WD8U{display:none;place-content:center}@container (min-width: 520px) and (max-width: 616px){.MultipleIncontentWide_root__4WD8U{display:grid}}.MultipleIncontentLarge_root__pGIAn{display:none;grid-template-columns:repeat(auto-fit,minmax(max-content,336px));place-content:center;gap:16px}@container (min-width: 688px){.MultipleIncontentLarge_root__pGIAn{display:grid}}.SlideRecs_root__likA5{--card-hover-background:var(--blue-gray-200);display:flex;flex-direction:column;gap:20px}.SlideRecs_root__likA5 h2{font-size:24px;font-weight:600}@media (max-width:1050px){.SlideRecs_root__likA5{display:none}}.SlideRecs_root__likA5 .SlideRecs_cards__Lbxtt{display:grid;grid-gap:16px;gap:16px}.SlideRecs_root__likA5 .SlideRecs_card__txc2D{position:relative;grid-template-columns:180px 1fr;grid-template-rows:auto;gap:16px}.SlideRecs_root__likA5 .slideshow-thumbnail{box-shadow:0 0 0 1px var(--blue-gray-200)}.SlideRecs_root__likA5 .slideshow-card-content{padding-block:0;padding-inline-end:36px;gap:8px}.SlideRecs_root__likA5 .slideshow-title{line-height:1;margin-block-end:0}.SlideRecs_root__likA5 .SlideRecs_wrapper__21j_w{display:flex;gap:8px;align-items:center}.SlideRecs_root__likA5 .SlideRecs_wrapper__21j_w a,.SlideRecs_root__likA5 .SlideRecs_wrapper__21j_w span{font-size:12px}.SlideRecs_root__likA5 .SlideRecs_save__RR8dD{position:absolute;top:8px;right:8px}.SlideRecs_root__likA5 .SlideRecs_author__zlhWO{color:var(--blue-gray-700);font-weight:600;text-decoration:none;z-index:2}.SlideRecs_root__likA5 .SlideRecs_author__zlhWO:first-letter{text-transform:uppercase}.SlideRecs_root__likA5 .SlideRecs_card__txc2D .slideshow-title{font-size:16px}.SlideRecs_root__likA5 .SlideRecs_description__0bGsx{color:var(--blue-gray-700);font-size:14px;display:-webkit-box;overflow:hidden;line-clamp:1;-webkit-line-clamp:1;-webkit-box-orient:vertical}.SlideRecs_root__likA5 .SlideRecs_dot__Db7HR{font-size:16px}.SlideRecs_root__likA5 .SlideRecs_tags__RCA1q{display:flex;flex-wrap:wrap;align-items:center;gap:8px}.SlideRecs_root__likA5 .SlideRecs_tags__RCA1q span{display:grid;place-content:center;height:20px;background-color:var(--alice-blue-600);border-radius:100vmax;color:var(--blue-gray-700);font-size:11px;font-weight:600;padding-inline:12px;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap;text-transform:capitalize;z-index:2}.RecWithPopover_root__iNXfW{width:330px;display:flex;flex-direction:column;gap:8px}.RecWithPopover_root__iNXfW>*{line-height:18px}.RecWithPopover_root__iNXfW h3{font-size:16px;font-weight:600}.RecWithPopover_root__iNXfW p{flex:1 1;color:var(--blue-gray-600);font-size:14px;max-width:100%;display:-webkit-box;line-clamp:5;-webkit-line-clamp:5;-webkit-box-orient:vertical;overflow-y:hidden}.RecWithPopover_hasSiblingsWithPopover__dlaxN{border-radius:4px;padding:12px}.RecWithPopover_hasPopover__G8_rr:hover:not(:has(:is(.save:hover,.author:hover))){background-color:var(--card-hover-background)}.SlideshowCard_root__pD8t4{position:relative;display:grid;grid-template-rows:max-content minmax(0,1fr);grid-template-columns:minmax(0,1fr);align-content:flex-start;color:var(--blue-gray-600)}.SlideshowCard_root__pD8t4:hover .SlideshowCard_thumb__86aJk{scale:1.02}.SlideshowCard_content__xh7kV{display:flex;flex-direction:column;-moz-column-gap:8px;column-gap:8px;padding:16px 0}.SlideshowCardLink_root__p8KI7{position:absolute;inset:0;z-index:1;margin:4px}.Thumbnail_root__qLW0K{--ease:cubic-bezier(0.2,0,0,1);position:relative;background-color:var(--blue-gray-100);border:1px solid var(--blue-gray-100);border-radius:8px;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);scale:1;transition:scale .2s var(--ease);overflow:hidden}.Thumbnail_thumb__UXO3a{--reveal-delay:calc(30ms * var(--index));position:absolute;inset:0;width:100%;height:100%;opacity:0;transition:opacity .3s var(--ease);transition-delay:var(--reveal-delay)}.Thumbnail_loaded__XOJ5p{opacity:1}.Thumbnail_blur__opK6A{filter:blur(8px)}.Thumbnail_cover__1zsIi{-o-object-fit:cover;object-fit:cover}.Thumbnail_contain__K6M0d{-o-object-fit:contain;object-fit:contain}.SlideshowTitle_root__2VccW{display:-webkit-box;color:var(--blue-gray-900);font-size:18px;font-weight:600;line-height:1.2;margin-bottom:8px;white-space:break-spaces;word-break:break-word;-webkit-box-orient:vertical}.RecSaveButton_root__0CS9m{grid-area:d}.RecSaveButton_icon__btwCp{fill:currentColor;z-index:2}@media (max-width:928px){.RecSaveButton_root__0CS9m{display:none}}.SlideshowAuthor_root__IkT1_{color:var(--celadon-blue);font-weight:600;text-decoration:underline;-webkit-text-decoration-color:transparent;text-decoration-color:transparent;text-decoration-thickness:1.5px;text-underline-offset:2px;transition:-webkit-text-decoration-color .2s ease-out;transition:text-decoration-color .2s ease-out;transition:text-decoration-color .2s ease-out,-webkit-text-decoration-color .2s ease-out;z-index:2}.SlideshowAuthor_root__IkT1_:hover{-webkit-text-decoration-color:var(--celadon-blue);text-decoration-color:var(--celadon-blue)}.CountTag_root__y1hE1,.SplitDot_root__lTZDc{color:var(--blue-gray-600);font-weight:400}.SlideshowStats_root__EQOR1{display:flex;align-items:center;gap:6px}.SlideshowStats_text___WD7l{color:var(--blue-gray-600)}.BelowReaderAd_root__NKeGg{margin-top:16px;margin-bottom:60px}.BelowReaderAd_root__NKeGg.BelowReaderAd_desktop__7_JN7{display:block;justify-items:flex-start}.BelowReaderAd_root__NKeGg.BelowReaderAd_mobile__08T3d{display:none}.BelowReaderAd_root__NKeGg .fallback-ad{justify-self:flex-start}@media screen and (max-width:928px){.BelowReaderAd_root__NKeGg.BelowReaderAd_desktop__7_JN7{display:none}.BelowReaderAd_root__NKeGg.BelowReaderAd_mobile__08T3d{display:block;justify-items:center}}.Sidebar_root__1BbNu{width:var(--sidebar-size);max-height:100dvh;overflow:clip scroll;position:sticky;top:var(--metadata-toolbar-offset,0);display:flex;flex-direction:column;padding-inline-start:28px;padding-block-start:32px;padding-block-end:430px}.Sidebar_root__1BbNu.Sidebar_withSidebarAds__0w0dT{max-height:unset;overflow:unset;position:static;top:unset;display:grid;grid-template-rows:repeat(var(--slots),1fr);padding-inline:28px}@media (max-width:1050px){.Sidebar_root__1BbNu,.Sidebar_root__1BbNu.Sidebar_withSidebarAds__0w0dT{display:none}}.AboveRecsAd_root__iTmTR{min-height:280px;margin-block-end:32px}.AboveRecsAd_root__iTmTR .freestar-ad-container{place-content:flex-start}@media (max-width:1050px){.AboveRecsAd_root__iTmTR .freestar-ad-container{place-content:center}}.AboveRecsAd_mobileAd__LYgqf{display:none}.AboveRecsAd_desktopAd__ymykj{display:block}@media (max-width:1050px){.AboveRecsAd_mobileAd__LYgqf{display:block}.AboveRecsAd_desktopAd__ymykj{display:none}}.RailRecommendations_root__zqtZQ{display:flex;flex-direction:column;gap:8px;padding-block-end:24px}.RailRecommendations_title__kt1D2{font-size:24px;color:var(--blue-gray-900);font-weight:600;margin-block-end:20px}@media only screen and (min-width:929px){.RailRecommendations_hidden__7Ct2B{display:none}}.RailCard_root__rZUGY{--card-hover-background:var(--blue-gray-100);width:100%;position:relative;border-radius:8px;padding-block:12px;padding-inline:8px}.RailCard_root__rZUGY.slideshow-card{grid-template-columns:160px minmax(0,1fr);grid-template-rows:auto;gap:12px}.RailCard_root__rZUGY .slideshow-card-content{padding:0}.RailCard_actionContainer__eXUHT{display:grid;grid-template-columns:minmax(0,1fr) 28px;grid-column-gap:8px;-moz-column-gap:8px;column-gap:8px}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-author,.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-stats,.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-title{grid-area:unset}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-author,.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-stats{grid-column:1/-1}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-author{grid-row:2}.RailCard_root__rZUGY .RailCard_actionContainer__eXUHT .slideshow-stats{grid-row:3}.RailCard_link__d3BBm{z-index:1}.RailCard_info__Oqm6G{min-width:0;display:flex;flex-direction:column;justify-content:center;word-break:break-word}.RailCard_root__rZUGY .RailCard_title__Tvfiv{font-size:16px;margin-bottom:0;grid-area:a;word-break:break-word}.RailCard_root__rZUGY .RailCard_stats__ZvZms{margin-top:12px}.RailCard_stats__ZvZms .text{font-size:12px}.RailCard_root__rZUGY .RailCard_author__JYeYZ{color:var(--blue-gray-700);margin-top:8px;text-decoration:none}@media screen and (min-width:929px){.RailCard_root__rZUGY:hover{background-color:var(--blue-gray-100)}}.RelatedContent_root__29Np1{background-color:var(--blue-gray-100);border-top:1px solid var(--blue-gray-200);border-bottom:1px solid var(--blue-gray-200);padding-block:32px;position:relative}.RelatedContent_wrapper__riU7l{display:grid;grid-template-columns:minmax(0,1fr);grid-gap:32px;gap:32px;max-width:var(--max-content-width);margin-inline:auto}.RelatedContent_title__QUhpL{text-align:center;font-size:32px;font-weight:700}.RelatedContent_root__29Np1 .bottom-recs{display:grid}.RelatedContent_root__29Np1 .rail-recs{display:none}@media screen and (max-width:520px){.RelatedContent_root__29Np1 .bottom-recs{display:none}.RelatedContent_root__29Np1 .rail-recs{display:flex}.RelatedContent_wrapper__riU7l{padding-inline:16px}}.BottomRecommendation_root__7aU9w{display:grid;grid-gap:4px;gap:4px;padding-inline:24px}.BottomRecommendation_title__SRj68{font-size:22px;font-weight:600}.BottomRecommendation_count__4HpLo{color:var(--blue-gray-600);font-size:16px;font-weight:400}.Slider_root__c0Jo8{position:relative;display:grid}.Slider_scroller__KHjw4{display:flex;gap:20px;overflow:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;max-inline-size:100%;min-block-size:100%;touch-action:pan-x;-ms-overflow-style:none;scrollbar-width:none}.Slider_scroller__KHjw4::-webkit-scrollbar{display:none}.Slider_scroller__KHjw4>*{flex-grow:1;flex-shrink:0;scroll-snap-align:start}.Slider_scroller__KHjw4>:last-child{scroll-snap-align:end}.Slider_arrow__8LCca{display:grid;place-content:center;width:36px;height:36px;position:absolute;top:50%;background:var(--white);border:1px solid var(--blue-gray-200);border-radius:100vmax;box-shadow:0 2px 4px 0 rgba(0,0,0,.25);color:var(--blue-gray-800);padding:0;opacity:1;visibility:visible;pointer-events:all;transition:opacity .2s ease-in-out,visibility .2s ease-in-out;transition-delay:0s;cursor:pointer;z-index:1}.Slider_prev__YMssa{left:0;translate:-50% -50%}.Slider_next__fa9IO{right:0;translate:50% -50%}.Slider_hidden__rs7nK{opacity:0;visibility:hidden;pointer-events:none}.BottomRecommendationCard_root__gffTk{inline-size:clamp(220px,12.63rem + 3.45vw,260px);position:relative;border-radius:8px;background-color:#fff;border:1px solid var(--blue-gray-200)}.BottomRecommendationCard_root__gffTk .slideshow-thumbnail{border-radius:0;border-top-left-radius:8px;border-top-right-radius:8px}.BottomRecommendationCard_root__gffTk .slideshow-card-content{padding:16px}.BottomRecommendationCard_root__gffTk .slideshow-author{max-width:50%;word-break:break-all;white-space:break-spaces;-webkit-line-clamp:1;display:-webkit-box;-webkit-box-orient:vertical}.BottomRecommendationCard_root__gffTk .slideshow-stats{margin-block-start:auto}.BottomRecommendationCard_root__gffTk:focus,.BottomRecommendationCard_root__gffTk:focus-visible{outline:none;border-color:var(--celadon-blue);border-width:1px}.BottomRecommendationCard_root__gffTk .BottomRecommendationCard_text__5jKNE{display:-webkit-box;-webkit-box-orient:vertical;white-space:break-spaces;word-break:break-word;-webkit-line-clamp:1}.BottomRecommendationCard_link__pHORq:before{content:"";position:absolute;inset:0;z-index:1}.BottomRecommendationCard_metaLine__shwPk{display:flex;gap:8px;align-items:center;white-space:nowrap;grid-area:c}.ScribdRecommendation_root__t3ezS{display:grid;grid-gap:16px;gap:16px;padding:0 24px}.ScribdRecommendation_header__Jw_M1{display:grid;grid-template-columns:1fr max-content}.ScribdRecommendation_title__JZ5p7{font-size:22px;font-weight:600}.ScribdRecommendation_link__4DVQz{align-self:end;grid-column:2;grid-row:1/span 2;color:var(--celadon-blue-dark)}.ScribdRecommendation_link__4DVQz:hover{color:var(--celadon-blue)}.ScribdRecommendationCard_root__ef2Y_{--ease:cubic-bezier(0.2,0,0,1);--rec-bg-1:#f1e3e3;--rec-bg-2:#f6f4e3;--rec-bg-3:#e1eaec;--rec-bg-4:#efebef;--rec-bg-5:#f1f0f0;--rec-bg-6:#f4eadb;--rec-bg-7:#eaeee7;--rec-bg-8:#e3e8ef;--rec-bg-9:#f1eee6;max-width:172px;position:relative;height:100%;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:max-content minmax(0,1fr);align-content:flex-start;background-color:var(--white);border:1px solid var(--blue-gray-200);box-shadow:none;transition:box-shadow .2s ease-in-out;text-decoration:none}.ScribdRecommendationCard_root__ef2Y_:hover{box-shadow:0 2px 10px rgba(0,0,0,.1)}.ScribdRecommendationCard_thumb__5VVNh{position:relative;display:flex;background-color:var(--blue-gray-200);margin-block-start:24px;margin-inline:20px}.ScribdRecommendationCard_root__ef2Y_:before{position:absolute;inset:0;aspect-ratio:19/16;content:"";background-color:var(--block-color)}.ScribdRecommendationCard_thumb__5VVNh img{--reveal-delay:calc(30ms * var(--card-index));aspect-ratio:inherit;box-shadow:0 4px 6px rgba(0,0,0,.2);-o-object-fit:cover;object-fit:cover;opacity:0;visibility:hidden;transition:opacity .3s var(--ease);transition-delay:var(--reveal-delay)}.ScribdRecommendationCard_loaded__FTN_f img{opacity:1;visibility:visible}.ScribdRecommendationCard_content__ObcvL{display:flex;flex-direction:column;padding-block:20px;padding-inline:16px}.ScribdRecommendationCard_content__ObcvL .rating{padding-block-start:8px;margin-block-start:auto}@media (max-width:928px){.ScribdRecommendationCard_content__ObcvL .rating{flex-direction:column;align-items:flex-start}}.Rating_root__fgZQJ{display:flex;align-items:center;-moz-column-gap:6px;column-gap:6px;white-space:nowrap}.Rating_root__fgZQJ :first-child{color:#e47b01;font-size:16px;line-height:1}.Rating_root__fgZQJ :last-child{color:var(--blue-gray-600);font-size:14px;letter-spacing:-.25px}.Transcript_root__Vrf6Q{width:100%;max-width:var(--max-content-width);display:grid;grid-gap:8px;gap:8px;padding:32px 16px;margin-inline:auto;position:relative;background:#fff}.Transcript_title__YgAka{display:flex;align-items:center;gap:4px;font-weight:300;word-break:break-word}.Transcript_list__faItj{list-style-type:none;padding-inline-start:0;word-break:break-word}.Transcript_link__MLbGS{color:var(--celadon-blue);font-weight:700;line-height:22px;text-decoration:none;cursor:pointer}.EditorsNotes_root__3PcDF{padding:32px 16px;margin:0 auto}.EditorsNotes_heading__XR9E6{font-weight:700;font-size:22px}.EditorsNotes_list__NcG5Y{padding-left:30px;font-size:18px;font-style:italic;color:var(--blue-gray-600)}.EditorsNotes_item__ebBbj{word-break:break-word}@media screen and (min-width:1696px){.EditorsNotes_root__3PcDF{max-width:1688px}}.FixedDownloadButton_root__14xtQ{display:none}@media screen and (max-width:928px){.FixedDownloadButton_root__14xtQ{position:sticky;bottom:0;display:flex;justify-content:right;z-index:3;padding:16px}}.Modal_root__TYkzh[open]{opacity:1;animation:Modal_slide-in__GHXut .3s ease-out}.Modal_root__TYkzh{--max-height:calc(100dvb - var(--header-height));--slide-from:calc(-50% + 8px);--slide-to:-50%;--title-size:80px;max-width:100%;max-height:var(--max-height);top:50%;left:50%;translate:-50% -50%;box-shadow:0 0 0 1px rgba(9,30,66,.08),0 2px 1px rgba(9,30,66,.08),0 0 20px -6px rgba(9,30,66,.3);border:0;border-radius:var(--border-radius);padding:0;opacity:0;animation:Modal_slide-out__m_Ov2 .2s ease-in;transition:display allow-discrete .3s,overlay allow-discrete .3s;overflow:clip}@starting-style{.Modal_root__TYkzh[open]{opacity:0}}.Modal_root__TYkzh.Modal_small__hupRE{width:400px}.Modal_root__TYkzh.Modal_medium__j8NOV{width:600px}.Modal_root__TYkzh.Modal_large__ygVmr{width:800px}.Modal_root__TYkzh.Modal_xlarge__HeXWk{width:960px}.Modal_wrapper__4UTGq{position:relative;display:flex;flex-direction:column}.Modal_wrapper__4UTGq>h1+*{flex:1 1;max-height:calc(var(--max-height) - var(--title-size));overflow:clip auto}@media screen and (max-width:520px){.Modal_root__TYkzh,.Modal_root__TYkzh.Modal_large__ygVmr,.Modal_root__TYkzh.Modal_medium__j8NOV,.Modal_root__TYkzh.Modal_small__hupRE,.Modal_root__TYkzh.Modal_xlarge__HeXWk{width:100vw}}@media screen and (max-width:928px){.Modal_root__TYkzh.Modal_bottomPlacement__BUbfp{--slide-from:8px;--slide-to:0;width:100vw;top:unset;bottom:0;translate:-50% 0;border-bottom-left-radius:0;border-bottom-right-radius:0}}@keyframes Modal_slide-in__GHXut{0%{translate:-50% var(--slide-from);opacity:0}to{translate:-50% var(--slide-to);opacity:1}}@keyframes Modal_slide-out__m_Ov2{0%{translate:-50% var(--slide-to);opacity:1}to{translate:-50% var(--slide-from);opacity:0}}.Modal_root__TYkzh::backdrop{background-color:transparent;transition:display allow-discrete .3s,overlay allow-discrete .3s,background-color .3s}.Modal_root__TYkzh[open]::backdrop{background-color:rgba(0,0,0,.6)}@starting-style{.Modal_root__TYkzh[open]::backdrop{background-color:transparent}}.Modal_title__xhSfl{height:var(--title-size);display:flex;align-items:center;color:var(--blue-gray-900);font-size:20px;font-weight:600;border-bottom:1px solid var(--blue-gray-200);padding-inline-start:20px;padding-inline-end:60px}.Modal_title__xhSfl:first-letter{text-transform:capitalize}.Modal_content__R1F4d{padding-inline:20px;padding-block:24px}.Modal_actions__t63hZ{display:flex;align-items:center;justify-content:flex-end;gap:24px;padding-inline:20px;padding-block:16px}.CloseButton_root__JCTRm{position:absolute;right:16px;top:16px;width:40px;height:40px;display:grid;place-content:center;color:var(--blue-gray-600);border-radius:100vmax;background-color:transparent;border:0;padding:0;margin:0;transition:background-color .2s ease-in-out;cursor:pointer}.CloseButton_root__JCTRm:hover{background-color:rgba(var(--blue-gray-600-rgb),.05)}.ReportForm_root__RNqAc{display:grid;grid-gap:10px;gap:10px}.ReportForm_selectField__Kyyaj{max-width:unset!important}.LikeModal_more__R9uAk{justify-self:center;color:var(--celadon-blue);font-weight:500;opacity:0;visibility:hidden;transition:opacity .2s ease-in-out,visibility .2s ease-in-out}.LikeModal_more__R9uAk.LikeModal_visible__t1vr4{opacity:1;visibility:visible;transition-delay:0s}.LikesUserList_root__RMFUk{list-style:none;padding:0 0 16px;margin:0}.LikesUserList_root__RMFUk>li{display:grid}.LikesUserList_root__RMFUk>li:last-child .LikesUserList_link__NeMA0{border-bottom:none}.LikesUserList_link__NeMA0{display:grid;grid-template-columns:repeat(2,max-content) 1fr;grid-template-rows:repeat(2,min-content);grid-template-areas:"avatar username summary" "avatar title title";grid-column-gap:12px;-moz-column-gap:12px;column-gap:12px;grid-row-gap:0;row-gap:0;border-bottom:1px solid var(--blue-gray-200);padding:8px 0;text-decoration:none}.LikesUserList_avatar__VRXz2{grid-area:avatar}.LikesUserList_username__c84om{grid-area:username}.LikesUserList_summary___gbSG{grid-area:summary}.LikesUserList_title__UF0V6{grid-area:title}.ViewModal_content__GPKXy{padding-block:0}.ViewModal_row__xLxnz{display:flex;justify-content:space-between;border-bottom:1px dashed var(--blue-gray-300);color:var(--blue-gray-800);padding:16px 0}.ViewModal_row__xLxnz:last-child{border-bottom:none}.ConfirmRemoveSavedModal_description__2EAEu{color:var(--blue-gray-800);font-weight:400;font-size:16px;line-height:20px;padding:24px 0}div.SaveToNewListModal_input__Fi90k{max-width:unset;margin-bottom:24px}.SaveToNewListModal_checkboxWrapper__y_w0m .SaveToNewListModal_checkboxLabel__lMiU9:hover,.SaveToNewListModal_checkboxWrapper__y_w0m:hover .SaveToNewListModal_checkboxLabel__lMiU9,.SaveToNewListModal_checkboxWrapper__y_w0m:hover .SaveToNewListModal_input__Fi90k{color:var(--blue-gray-800)}.SaveToNewListModal_checkboxWrapper__y_w0m .SaveToNewListModal_checkboxLabel__lMiU9{color:var(--blue-gray-800);font-weight:400;font-size:14px;flex-direction:column;display:flex;line-height:18px}.SaveToNewListModal_imageContainer__Dx4nD{display:flex;flex-direction:column;justify-content:center;align-items:center;width:178px;margin:0 auto 24px}.SaveToNewListModal_imageContainer__Dx4nD img{height:100px}.SaveToNewListModal_errorContainer__FBZPH{margin:16px 0}
Svoboda | Graniru | BBC Russia | Golosameriki | Facebook
SlideShare a Scribd company logo
$10 thousand per minute

of downtime: architecture,

queues, streaming and fintech
Max Baginskiy
Solidgate
About me
Head of Engineering

PreviouslyTech Lead and Platform engineer
Head of Engineering

PreviouslyTech Lead and Platform engineer
Head of Engineering

PreviouslyTech Lead and Platform engineer
10 yrs in Software Engineering

Last 6 years Go, fan of DevOps
10 yrs in Software Engineering

Last 6 years Go, fan of DevOps
10 yrs in Software Engineering

Last 6 years Go, fan of DevOps
Build teams (5 teams, 30+ people hired)

And architecture
Build teams (5 teams, 30+ people hired)

And architecture
Build teams (5 teams, 30+ people hired)

And architecture
Agenda
Company intro
Architecture of the system
Queues and Streams to choose from
Low latency streaming using outbox
CDC to our solution - comparison
Questions.
Company intro
Architecture of the system
Queues and Streams to choose from
Low latency streaming using outbox
CDC to our solution - comparison
Questions.
Company intro
Architecture of the system
Queues and Streams to choose from
Low latency streaming using outbox
CDC to our solution - comparison
Questions.
About company
7+ years online
7+ years online
7+ years online 70 engineers
50 SW engineers

20 Infra + Data engineers + AQA
70 engineers
50 SW engineers

20 Infra + Data engineers + AQA
70 engineers
50 SW engineers

20 Infra + Data engineers + AQA
PCI DSS Compliant
PCI DSS Compliant
PCI DSS Compliant European Acquirer
European Acquirer
European Acquirer
"$10 thousand per minute of downtime: architecture, queues, streaming and fintech", Max Baginskiy
Business figures
2.5b$
annually
2.5b$
annually
2.5b$
annually
15-18m
tx monthly
15-18m
tx monthly
15-18m
tx monthly
10k$
1 min of downtime
10k$
1 min of downtime
10k$
1 min of downtime
40+
integrated payment methods

and providers
40+
integrated payment methods

and providers
40+
integrated payment methods

and providers
ALBTraffic
We have 100x less traffic on ALB

during high season than Shopify
We have 100x less traffic on ALB

during high season than Shopify
We have 100x less traffic on ALB

during high season than Shopify
Stripe served 250mil API calls

in 2020 perday
Stripe served 250mil API calls

in 2020 perday
Stripe served 250mil API calls

in 2020 perday
Kafka Producer
We started integrating kafka lastyear
We started integrating kafka lastyear
We started integrating kafka lastyear 20 rps average
20 rps average
20 rps average 2 mil events perday
2 mil events perday
2 mil events perday
RabbitMQ Producer
100-120 rps average
100-120 rps average
100-120 rps average 10 mil events perday
10 mil events perday
10 mil events perday
Logs
1.5-2k rps of logs
1.5-2k rps of logs
1.5-2k rps of logs 150 mil events per day. 200-300 GB of logs daily
150 mil events per day. 200-300 GB of logs daily
150 mil events per day. 200-300 GB of logs daily
Architecture
Letthestory

begin
Go
Letthestory

begin
Go
Letthestory

begin
Go
Go
Go
Project “Taxer”
Non functional requirements
Durability out of the box
Durability out of the box
Durability out of the box Queue replay
Queue replay
Queue replay
Single active consumer support
Single active consumer support
Single active consumer support
Easy to setup and to maintain
Easy to setup and to maintain
Easy to setup and to maintain Partitioning
Partitioning
Partitioning
Easy scaling for publisher and consumer
Easy scaling for publisher and consumer
Easy scaling for publisher and consumer
Extensiblity: schema registry support, dynamic routing, enrichment
Extensiblity: schema registry support, dynamic routing, enrichment
Extensiblity: schema registry support, dynamic routing, enrichment
NFR - explanation
What if message is lost in between services

while processing andwe retry payment?
What if message is lost in between services

while processing andwe retry payment?
What if message is lost in between services

while processing andwe retry payment?
What if message is lost in between

callback service and callback processor?
What if message is lost in between

callback service and callback processor?
What if message is lost in between

callback service and callback processor?
What if message is lost in between

payment and finance systems?
What if message is lost in between

payment and finance systems?
What if message is lost in between

payment and finance systems?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
what if …what if …what if …?
RabbitMQ dive in
Erlang
Written in Erlang. Erlang made by Ericssonwhich
makes telecommunication devices.
Erlang
Written in Erlang. Erlang made by Ericssonwhich
makes telecommunication devices.
Erlang
Written in Erlang. Erlang made by Ericssonwhich
makes telecommunication devices.
Proof of fail-safety
ATM AXD301 example, Calculated uptime
99,9999999%, only one problem permany years.
Proof of fail-safety
ATM AXD301 example, Calculated uptime
99,9999999%, only one problem permany years.
Proof of fail-safety
ATM AXD301 example, Calculated uptime
99,9999999%, only one problem permany years.
Mnesia as storage
Mnesia doesn’t support recovery from split brain and
othertypes of failures.
Mnesia as storage
Mnesia doesn’t support recovery from split brain and
othertypes of failures.
Mnesia as storage
Mnesia doesn’t support recovery from split brain and
othertypes of failures.
RabbitMQ Durability
Mechanisms
Publisher confirms is a MUST have
RabbitMQ can store data to Disk and
different autoheal modes
Different types of queues: Quorum,
Mirrored
Have Streaming in “beta”.
Mechanisms
Publisher confirms is a MUST have
RabbitMQ can store data to Disk and
different autoheal modes
Different types of queues: Quorum,
Mirrored
Have Streaming in “beta”.
Mechanisms
Publisher confirms is a MUST have
RabbitMQ can store data to Disk and
different autoheal modes
Different types of queues: Quorum,
Mirrored
Have Streaming in “beta”.
What if publisher confirms
disabled?
Delivery after exchange might not
happen
Persistence might not happen
Few replicas might not acknowledge
message in Quorum
Overwhelmed Clusterwill not accept
messages but publisherwill not
know.
What if publisher confirms
disabled?
Delivery after exchange might not
happen
Persistence might not happen
Few replicas might not acknowledge
message in Quorum
Overwhelmed Clusterwill not accept
messages but publisherwill not
know.
What if publisher confirms
disabled?
Delivery after exchange might not
happen
Persistence might not happen
Few replicas might not acknowledge
message in Quorum
Overwhelmed Clusterwill not accept
messages but publisherwill not
know.
Quorum queues
+ Pros
Have Consensus built in
Data written to disk, metadata in memory
Can easily handle restarts.
+ Pros
Have Consensus built in
Data written to disk, metadata in memory
Can easily handle restarts.
+ Pros
Have Consensus built in
Data written to disk, metadata in memory
Can easily handle restarts.
− Cons
Doesn’t scale well - millions of messages after
restart can replicate hours.
Doen’t have “replay” mechanism
Consumers doesn’t scale
Doesn’t preserve order of messages.
− Cons
Doesn’t scale well - millions of messages after
restart can replicate hours.
Doen’t have “replay” mechanism
Consumers doesn’t scale
Doesn’t preserve order of messages.
− Cons
Doesn’t scale well - millions of messages after
restart can replicate hours.
Doen’t have “replay” mechanism
Consumers doesn’t scale
Doesn’t preserve order of messages.
Split brain
Split brain - autoheal
ignore
Usewhen network reliability is the highest practically possible and node availability is of topmost importance.
ignore
Usewhen network reliability is the highest practically possible and node availability is of topmost importance.
ignore
Usewhen network reliability is the highest practically possible and node availability is of topmost importance.
pause_minority
Appropriatewhen clustering across racks oravailability zones in a single region and the probability of losing a majority
of nodes (zones) at once is considered to bevery low.
pause_minority
Appropriatewhen clustering across racks oravailability zones in a single region and the probability of losing a majority
of nodes (zones) at once is considered to bevery low.
pause_minority
Appropriatewhen clustering across racks oravailability zones in a single region and the probability of losing a majority
of nodes (zones) at once is considered to bevery low.
autoheal
Appropriatewhen are more concernedwith continuity of service thanwith data consistency across nodes.
autoheal
Appropriatewhen are more concernedwith continuity of service thanwith data consistency across nodes.
autoheal
Appropriatewhen are more concernedwith continuity of service thanwith data consistency across nodes.
Summary - noway to guarantee that autohealwillwork properly
Summary - noway to guarantee that autohealwillwork properly
Summary - noway to guarantee that autohealwillwork properly
RabbitMQ streaming, problem #1
RabbitMQ streaming. Go client, issue #2
RabbitMQ streaming. Go client, issue #3
RabbitMQ + RabbitMQ streaming
Newfeature that not a lot of companies use.
Newfeature that not a lot of companies use.
Newfeature that not a lot of companies use.
Go client is not ready,what about Python orNode.js I’m aftaid to ask.
Go client is not ready,what about Python orNode.js I’m aftaid to ask.
Go client is not ready,what about Python orNode.js I’m aftaid to ask.
Hard to support. Requires updates of Erlang and then RabbitMQ.
Hard to support. Requires updates of Erlang and then RabbitMQ.
Hard to support. Requires updates of Erlang and then RabbitMQ.
Streaming is a plugin that requires specificversion of RabbitMQ.
Streaming is a plugin that requires specificversion of RabbitMQ.
Streaming is a plugin that requires specificversion of RabbitMQ.
Not made for fintech: lack of properdurability, lack of functionality.
Not made for fintech: lack of properdurability, lack of functionality.
Not made for fintech: lack of properdurability, lack of functionality.
"$10 thousand per minute of downtime: architecture, queues, streaming and fintech", Max Baginskiy
Kafka dive in
Java
Written in Java by Linkedin and then
opensourced and licenced under
Apache licence.
Java
Written in Java by Linkedin and then
opensourced and licenced under
Apache licence.
Java
Written in Java by Linkedin and then
opensourced and licenced under
Apache licence.
Highly available and durable
HasWAL,works in cluster, saves data to
disk by default.
Highly available and durable
HasWAL,works in cluster, saves data to
disk by default.
Highly available and durable
HasWAL,works in cluster, saves data to
disk by default.
️Blazing fast
Sequentialwrites, zero copy.
️Blazing fast
Sequentialwrites, zero copy.
️Blazing fast
Sequentialwrites, zero copy.
Kafka dive in
Kafka uses optimizations around Sequentialwrites to optimize disk usagewith zero copy.
Kafka uses optimizations around Sequentialwrites to optimize disk usagewith zero copy.
Kafka uses optimizations around Sequentialwrites to optimize disk usagewith zero copy.
HasWAL log forreplication and durability.
HasWAL log forreplication and durability.
HasWAL log forreplication and durability.
Zookeeperas separate system tracks health of the cluster.
Zookeeperas separate system tracks health of the cluster.
Zookeeperas separate system tracks health of the cluster.
Canwork evenwithout Zookeeper.
Canwork evenwithout Zookeeper.
Canwork evenwithout Zookeeper.
Chaos engineering shows that Kafka is highlyavailable and durable solution.
Chaos engineering shows that Kafka is highlyavailable and durable solution.
Chaos engineering shows that Kafka is highlyavailable and durable solution.
Debezium
Debezium howto:
Debezium howto:
Debezium howto: Create a replication slot
Create a replication slot
Create a replication slot Run Debezium Java service in cluster
Run Debezium Java service in cluster
Run Debezium Java service in cluster Configurate itwith Groovy
Configurate itwith Groovy
Configurate itwith Groovy
Debezium
+ Pros
UsesWAL directly - doesn’t create
additional load toWAL(no additional
data iswritten).
Production ready, tested solution
Lowlatency. ️
+ Pros
UsesWAL directly - doesn’t create
additional load toWAL(no additional
data iswritten).
Production ready, tested solution
Lowlatency. ️
+ Pros
UsesWAL directly - doesn’t create
additional load toWAL(no additional
data iswritten).
Production ready, tested solution
Lowlatency. ️
− Cons
Howto replay data? Can you specify
Log Sequence Number?What if you
need to stream only a fraction ofwhat
iswritten inWAL
Missing Buf(protobuf on steroids)
Lowflexibility and hard configurability
DB Isolation.
Groovywhich is not easy to use
Random disconnects

and need to restart.
Transactional outbox
Why to use Transactional
Outbox?
Why to use Transactional
Outbox?
Why to use Transactional
Outbox?
Nor Kafka nor CDC can flexibly
re-stream data.
Nor Kafka nor CDC can flexibly
re-stream data.
Nor Kafka nor CDC can flexibly
re-stream data.
Without specific instruments
you can’t remove specific
events from Kafka.
Without specific instruments
you can’t remove specific
events from Kafka.
Without specific instruments
you can’t remove specific
events from Kafka.
Replay with Kafka will require
setup of additional services.
Replay with Kafka will require
setup of additional services.
Replay with Kafka will require
setup of additional services.
Consistent state with the usage
of Transactions.
Consistent state with the usage
of Transactions.
Consistent state with the usage
of Transactions.
Outboxtable-WAL
ID-ulid(sortableuuids).
ID-ulid(sortableuuids).
ID-ulid(sortableuuids). Bucket-partitioning.Read/Writepartitioning.
Bucket-partitioning.Read/Writepartitioning.
Bucket-partitioning.Read/Writepartitioning. Payload-jsonbodyofdomainmodel.
Payload-jsonbodyofdomainmodel.
Payload-jsonbodyofdomainmodel.
ChoosingGolib
confluent-kafka-go - CGO + librdkafka
confluent-kafka-go - CGO + librdkafka
confluent-kafka-go - CGO + librdkafka
ibm/sarama
ibm/sarama
ibm/sarama
segmentio/kafka-go
segmentio/kafka-go
segmentio/kafka-go
Outboxtable-WAL
BatchSize-usually10.
BatchSize-usually10.
BatchSize-usually10. Batch Timeout-100ms.
Batch Timeout-100ms.
Batch Timeout-100ms. RequiedAcks-allnodesshouldconfirmmessage.
RequiedAcks-allnodesshouldconfirmmessage.
RequiedAcks-allnodesshouldconfirmmessage.
Async-alseforsynchronouserrorhandling.
Async-alseforsynchronouserrorhandling.
Async-alseforsynchronouserrorhandling.
Kafka write latency
Schema registry -
“Speca first” approach - speedup development.
“Speca first” approach - speedup development.
“Speca first” approach - speedup development.
Backward compatibility support - linters.
Backward compatibility support - linters.
Backward compatibility support - linters.
Reusable “menthal model” = simplified migration from api to stream.
Reusable “menthal model” = simplified migration from api to stream.
Reusable “menthal model” = simplified migration from api to stream.
Client, server and models are generated for various language.
Client, server and models are generated for various language.
Client, server and models are generated for various language.
Simplified versioning.
Simplified versioning.
Simplified versioning.
Taxerv1 optionwe built
Update payment in Gate(kotlin).
Update payment in Gate(kotlin).
Update payment in Gate(kotlin).
Transaction: Save payment update
and create a record in Outbox.
Transaction: Save payment update
and create a record in Outbox.
Transaction: Save payment update
and create a record in Outbox.
Orderstreamer(Go) - reads batch
from outbox.
Orderstreamer(Go) - reads batch
from outbox.
Orderstreamer(Go) - reads batch
from outbox.
Publish data to Stream.
Publish data to Stream.
Publish data to Stream.
Update Offset in meta table.
Update Offset in meta table.
Update Offset in meta table.
Streamer
Meta table
Architecture comparison
v1 comparison with typical architecture
+ Pros
We have a full transaction log that can
be replayed, reworked, saved, fixed
Only 1 new tech - kafka
Streamer + Leaser = 200 lines of code +
800 lines of tests. It can be used as
library not a service
Buf/Go/PostgreSQL - everything
reused - maintenance simplified.
+ Pros
We have a full transaction log that can
be replayed, reworked, saved, fixed
Only 1 new tech - kafka
Streamer + Leaser = 200 lines of code +
800 lines of tests. It can be used as
library not a service
Buf/Go/PostgreSQL - everything
reused - maintenance simplified.
+ Pros
We have a full transaction log that can
be replayed, reworked, saved, fixed
Only 1 new tech - kafka
Streamer + Leaser = 200 lines of code +
800 lines of tests. It can be used as
library not a service
Buf/Go/PostgreSQL - everything
reused - maintenance simplified.
− Cons
WAL amplification - 2x. Transactional
outbox requires 1 more write to each
operation
High delay - 2 min for events
More CPU load than just reading from
WAL.
The end.
Nah, I’m kidding.
Orderstreamerdelay: 2min
You learned about 2 min delay
Orderstreamerdelay: 2min
ULIDs - doesn’t allowus to understand the commit orderof events and missing parts.
ULIDs - doesn’t allowus to understand the commit orderof events and missing parts.
ULIDs - doesn’t allowus to understand the commit orderof events and missing parts.
Solution-LogicalClock+Autoincrement
Logicalclocksallowadistributedsystemtoenforceapartialorderingofeventswithoutphysicalclocks.

Youalsocandetectmissingeventswiththem.
v2 Implementation
Auto increment instead of ULIDwill helpyou to report
and look formissing IDs. It’s more like “logical time”.
Auto increment instead of ULIDwill helpyou to report
and look formissing IDs. It’s more like “logical time”.
Auto increment instead of ULIDwill helpyou to report
and look formissing IDs. It’s more like “logical time”.
Look formissing ids for, save them in meta table for2
mins and restream themwhen theyappear.
Look formissing ids for, save them in meta table for2
mins and restream themwhen theyappear.
Look formissing ids for, save them in meta table for2
mins and restream themwhen theyappear.
v2Summary
+Pros
Reducesdelaytimefrom2minsto
literallyseconds
Wecanusethisapproachnotonlyin
reports/taxesbutalsoinprocessing.
+Pros
Reducesdelaytimefrom2minsto
literallyseconds
Wecanusethisapproachnotonlyin
reports/taxesbutalsoinprocessing.
+Pros
Reducesdelaytimefrom2minsto
literallyseconds
Wecanusethisapproachnotonlyin
reports/taxesbutalsoinprocessing.
−Cons
Orderingcanbebroken,butwecan
supportseveralmodelsofeventual
consistency
HigherDBCPUutilization.
−Cons
Orderingcanbebroken,butwecan
supportseveralmodelsofeventual
consistency
HigherDBCPUutilization.
−Cons
Orderingcanbebroken,butwecan
supportseveralmodelsofeventual
consistency
HigherDBCPUutilization.
The end.
Nah,I’mkiddingx2
v3readingWAL
WAL“reading”canbe
implementedinjust200lines

ofcodealongwithreplication
slotcreationandpublication
creation.
WAL“reading”canbe
implementedinjust200lines

ofcodealongwithreplication
slotcreationandpublication
creation.
WAL“reading”canbe
implementedinjust200lines

ofcodealongwithreplication
slotcreationandpublication
creation.
InPostgreSQLreplicationslots
youhaveanaccessto
received_lsn,latest_end_lsn.

Itseemslikeyoucanreplay
changes.
InPostgreSQLreplicationslots
youhaveanaccessto
, .

Itseemslikeyoucanreplay
changes.
received_lsn latest_end_lsn
InPostgreSQLreplicationslots
youhaveanaccessto
, .

Itseemslikeyoucanreplay
changes.
received_lsn latest_end_lsn
V3WALlib-NextTime
V3WALlib-NextTime
V3WALlib-NextTime
SeeyouatthenextHighload!
SeeyouatthenextHighload!
SeeyouatthenextHighload!
.....
.....
.....
making online payments simple

More Related Content

"$10 thousand per minute of downtime: architecture, queues, streaming and fintech", Max Baginskiy

  • 1. $10 thousand per minute of downtime: architecture, queues, streaming and fintech Max Baginskiy Solidgate
  • 2. About me Head of Engineering PreviouslyTech Lead and Platform engineer Head of Engineering PreviouslyTech Lead and Platform engineer Head of Engineering PreviouslyTech Lead and Platform engineer 10 yrs in Software Engineering Last 6 years Go, fan of DevOps 10 yrs in Software Engineering Last 6 years Go, fan of DevOps 10 yrs in Software Engineering Last 6 years Go, fan of DevOps Build teams (5 teams, 30+ people hired) And architecture Build teams (5 teams, 30+ people hired) And architecture Build teams (5 teams, 30+ people hired) And architecture
  • 3. Agenda Company intro Architecture of the system Queues and Streams to choose from Low latency streaming using outbox CDC to our solution - comparison Questions. Company intro Architecture of the system Queues and Streams to choose from Low latency streaming using outbox CDC to our solution - comparison Questions. Company intro Architecture of the system Queues and Streams to choose from Low latency streaming using outbox CDC to our solution - comparison Questions.
  • 4. About company 7+ years online 7+ years online 7+ years online 70 engineers 50 SW engineers 20 Infra + Data engineers + AQA 70 engineers 50 SW engineers 20 Infra + Data engineers + AQA 70 engineers 50 SW engineers 20 Infra + Data engineers + AQA PCI DSS Compliant PCI DSS Compliant PCI DSS Compliant European Acquirer European Acquirer European Acquirer
  • 6. Business figures 2.5b$ annually 2.5b$ annually 2.5b$ annually 15-18m tx monthly 15-18m tx monthly 15-18m tx monthly 10k$ 1 min of downtime 10k$ 1 min of downtime 10k$ 1 min of downtime 40+ integrated payment methods
 and providers 40+ integrated payment methods
 and providers 40+ integrated payment methods
 and providers
  • 7. ALBTraffic We have 100x less traffic on ALB
 during high season than Shopify We have 100x less traffic on ALB
 during high season than Shopify We have 100x less traffic on ALB
 during high season than Shopify Stripe served 250mil API calls
 in 2020 perday Stripe served 250mil API calls
 in 2020 perday Stripe served 250mil API calls
 in 2020 perday
  • 8. Kafka Producer We started integrating kafka lastyear We started integrating kafka lastyear We started integrating kafka lastyear 20 rps average 20 rps average 20 rps average 2 mil events perday 2 mil events perday 2 mil events perday
  • 9. RabbitMQ Producer 100-120 rps average 100-120 rps average 100-120 rps average 10 mil events perday 10 mil events perday 10 mil events perday
  • 10. Logs 1.5-2k rps of logs 1.5-2k rps of logs 1.5-2k rps of logs 150 mil events per day. 200-300 GB of logs daily 150 mil events per day. 200-300 GB of logs daily 150 mil events per day. 200-300 GB of logs daily
  • 14. Non functional requirements Durability out of the box Durability out of the box Durability out of the box Queue replay Queue replay Queue replay Single active consumer support Single active consumer support Single active consumer support Easy to setup and to maintain Easy to setup and to maintain Easy to setup and to maintain Partitioning Partitioning Partitioning Easy scaling for publisher and consumer Easy scaling for publisher and consumer Easy scaling for publisher and consumer Extensiblity: schema registry support, dynamic routing, enrichment Extensiblity: schema registry support, dynamic routing, enrichment Extensiblity: schema registry support, dynamic routing, enrichment
  • 15. NFR - explanation What if message is lost in between services
 while processing andwe retry payment? What if message is lost in between services
 while processing andwe retry payment? What if message is lost in between services
 while processing andwe retry payment? What if message is lost in between callback service and callback processor? What if message is lost in between callback service and callback processor? What if message is lost in between callback service and callback processor? What if message is lost in between payment and finance systems? What if message is lost in between payment and finance systems? What if message is lost in between payment and finance systems? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …? what if …what if …what if …?
  • 16. RabbitMQ dive in Erlang Written in Erlang. Erlang made by Ericssonwhich makes telecommunication devices. Erlang Written in Erlang. Erlang made by Ericssonwhich makes telecommunication devices. Erlang Written in Erlang. Erlang made by Ericssonwhich makes telecommunication devices. Proof of fail-safety ATM AXD301 example, Calculated uptime 99,9999999%, only one problem permany years. Proof of fail-safety ATM AXD301 example, Calculated uptime 99,9999999%, only one problem permany years. Proof of fail-safety ATM AXD301 example, Calculated uptime 99,9999999%, only one problem permany years. Mnesia as storage Mnesia doesn’t support recovery from split brain and othertypes of failures. Mnesia as storage Mnesia doesn’t support recovery from split brain and othertypes of failures. Mnesia as storage Mnesia doesn’t support recovery from split brain and othertypes of failures.
  • 17. RabbitMQ Durability Mechanisms Publisher confirms is a MUST have RabbitMQ can store data to Disk and different autoheal modes Different types of queues: Quorum, Mirrored Have Streaming in “beta”. Mechanisms Publisher confirms is a MUST have RabbitMQ can store data to Disk and different autoheal modes Different types of queues: Quorum, Mirrored Have Streaming in “beta”. Mechanisms Publisher confirms is a MUST have RabbitMQ can store data to Disk and different autoheal modes Different types of queues: Quorum, Mirrored Have Streaming in “beta”. What if publisher confirms disabled? Delivery after exchange might not happen Persistence might not happen Few replicas might not acknowledge message in Quorum Overwhelmed Clusterwill not accept messages but publisherwill not know. What if publisher confirms disabled? Delivery after exchange might not happen Persistence might not happen Few replicas might not acknowledge message in Quorum Overwhelmed Clusterwill not accept messages but publisherwill not know. What if publisher confirms disabled? Delivery after exchange might not happen Persistence might not happen Few replicas might not acknowledge message in Quorum Overwhelmed Clusterwill not accept messages but publisherwill not know.
  • 18. Quorum queues + Pros Have Consensus built in Data written to disk, metadata in memory Can easily handle restarts. + Pros Have Consensus built in Data written to disk, metadata in memory Can easily handle restarts. + Pros Have Consensus built in Data written to disk, metadata in memory Can easily handle restarts. − Cons Doesn’t scale well - millions of messages after restart can replicate hours. Doen’t have “replay” mechanism Consumers doesn’t scale Doesn’t preserve order of messages. − Cons Doesn’t scale well - millions of messages after restart can replicate hours. Doen’t have “replay” mechanism Consumers doesn’t scale Doesn’t preserve order of messages. − Cons Doesn’t scale well - millions of messages after restart can replicate hours. Doen’t have “replay” mechanism Consumers doesn’t scale Doesn’t preserve order of messages.
  • 20. Split brain - autoheal ignore Usewhen network reliability is the highest practically possible and node availability is of topmost importance. ignore Usewhen network reliability is the highest practically possible and node availability is of topmost importance. ignore Usewhen network reliability is the highest practically possible and node availability is of topmost importance. pause_minority Appropriatewhen clustering across racks oravailability zones in a single region and the probability of losing a majority of nodes (zones) at once is considered to bevery low. pause_minority Appropriatewhen clustering across racks oravailability zones in a single region and the probability of losing a majority of nodes (zones) at once is considered to bevery low. pause_minority Appropriatewhen clustering across racks oravailability zones in a single region and the probability of losing a majority of nodes (zones) at once is considered to bevery low. autoheal Appropriatewhen are more concernedwith continuity of service thanwith data consistency across nodes. autoheal Appropriatewhen are more concernedwith continuity of service thanwith data consistency across nodes. autoheal Appropriatewhen are more concernedwith continuity of service thanwith data consistency across nodes. Summary - noway to guarantee that autohealwillwork properly Summary - noway to guarantee that autohealwillwork properly Summary - noway to guarantee that autohealwillwork properly
  • 22. RabbitMQ streaming. Go client, issue #2
  • 23. RabbitMQ streaming. Go client, issue #3
  • 24. RabbitMQ + RabbitMQ streaming Newfeature that not a lot of companies use. Newfeature that not a lot of companies use. Newfeature that not a lot of companies use. Go client is not ready,what about Python orNode.js I’m aftaid to ask. Go client is not ready,what about Python orNode.js I’m aftaid to ask. Go client is not ready,what about Python orNode.js I’m aftaid to ask. Hard to support. Requires updates of Erlang and then RabbitMQ. Hard to support. Requires updates of Erlang and then RabbitMQ. Hard to support. Requires updates of Erlang and then RabbitMQ. Streaming is a plugin that requires specificversion of RabbitMQ. Streaming is a plugin that requires specificversion of RabbitMQ. Streaming is a plugin that requires specificversion of RabbitMQ. Not made for fintech: lack of properdurability, lack of functionality. Not made for fintech: lack of properdurability, lack of functionality. Not made for fintech: lack of properdurability, lack of functionality.
  • 26. Kafka dive in Java Written in Java by Linkedin and then opensourced and licenced under Apache licence. Java Written in Java by Linkedin and then opensourced and licenced under Apache licence. Java Written in Java by Linkedin and then opensourced and licenced under Apache licence. Highly available and durable HasWAL,works in cluster, saves data to disk by default. Highly available and durable HasWAL,works in cluster, saves data to disk by default. Highly available and durable HasWAL,works in cluster, saves data to disk by default. ️Blazing fast Sequentialwrites, zero copy. ️Blazing fast Sequentialwrites, zero copy. ️Blazing fast Sequentialwrites, zero copy.
  • 27. Kafka dive in Kafka uses optimizations around Sequentialwrites to optimize disk usagewith zero copy. Kafka uses optimizations around Sequentialwrites to optimize disk usagewith zero copy. Kafka uses optimizations around Sequentialwrites to optimize disk usagewith zero copy. HasWAL log forreplication and durability. HasWAL log forreplication and durability. HasWAL log forreplication and durability. Zookeeperas separate system tracks health of the cluster. Zookeeperas separate system tracks health of the cluster. Zookeeperas separate system tracks health of the cluster. Canwork evenwithout Zookeeper. Canwork evenwithout Zookeeper. Canwork evenwithout Zookeeper. Chaos engineering shows that Kafka is highlyavailable and durable solution. Chaos engineering shows that Kafka is highlyavailable and durable solution. Chaos engineering shows that Kafka is highlyavailable and durable solution.
  • 28. Debezium Debezium howto: Debezium howto: Debezium howto: Create a replication slot Create a replication slot Create a replication slot Run Debezium Java service in cluster Run Debezium Java service in cluster Run Debezium Java service in cluster Configurate itwith Groovy Configurate itwith Groovy Configurate itwith Groovy
  • 29. Debezium + Pros UsesWAL directly - doesn’t create additional load toWAL(no additional data iswritten). Production ready, tested solution Lowlatency. ️ + Pros UsesWAL directly - doesn’t create additional load toWAL(no additional data iswritten). Production ready, tested solution Lowlatency. ️ + Pros UsesWAL directly - doesn’t create additional load toWAL(no additional data iswritten). Production ready, tested solution Lowlatency. ️ − Cons Howto replay data? Can you specify Log Sequence Number?What if you need to stream only a fraction ofwhat iswritten inWAL Missing Buf(protobuf on steroids) Lowflexibility and hard configurability DB Isolation. Groovywhich is not easy to use Random disconnects
 and need to restart.
  • 30. Transactional outbox Why to use Transactional Outbox? Why to use Transactional Outbox? Why to use Transactional Outbox? Nor Kafka nor CDC can flexibly re-stream data. Nor Kafka nor CDC can flexibly re-stream data. Nor Kafka nor CDC can flexibly re-stream data. Without specific instruments you can’t remove specific events from Kafka. Without specific instruments you can’t remove specific events from Kafka. Without specific instruments you can’t remove specific events from Kafka. Replay with Kafka will require setup of additional services. Replay with Kafka will require setup of additional services. Replay with Kafka will require setup of additional services. Consistent state with the usage of Transactions. Consistent state with the usage of Transactions. Consistent state with the usage of Transactions.
  • 32. ChoosingGolib confluent-kafka-go - CGO + librdkafka confluent-kafka-go - CGO + librdkafka confluent-kafka-go - CGO + librdkafka ibm/sarama ibm/sarama ibm/sarama segmentio/kafka-go segmentio/kafka-go segmentio/kafka-go
  • 33. Outboxtable-WAL BatchSize-usually10. BatchSize-usually10. BatchSize-usually10. Batch Timeout-100ms. Batch Timeout-100ms. Batch Timeout-100ms. RequiedAcks-allnodesshouldconfirmmessage. RequiedAcks-allnodesshouldconfirmmessage. RequiedAcks-allnodesshouldconfirmmessage. Async-alseforsynchronouserrorhandling. Async-alseforsynchronouserrorhandling. Async-alseforsynchronouserrorhandling.
  • 35. Schema registry - “Speca first” approach - speedup development. “Speca first” approach - speedup development. “Speca first” approach - speedup development. Backward compatibility support - linters. Backward compatibility support - linters. Backward compatibility support - linters. Reusable “menthal model” = simplified migration from api to stream. Reusable “menthal model” = simplified migration from api to stream. Reusable “menthal model” = simplified migration from api to stream. Client, server and models are generated for various language. Client, server and models are generated for various language. Client, server and models are generated for various language. Simplified versioning. Simplified versioning. Simplified versioning.
  • 36. Taxerv1 optionwe built Update payment in Gate(kotlin). Update payment in Gate(kotlin). Update payment in Gate(kotlin). Transaction: Save payment update and create a record in Outbox. Transaction: Save payment update and create a record in Outbox. Transaction: Save payment update and create a record in Outbox. Orderstreamer(Go) - reads batch from outbox. Orderstreamer(Go) - reads batch from outbox. Orderstreamer(Go) - reads batch from outbox. Publish data to Stream. Publish data to Stream. Publish data to Stream. Update Offset in meta table. Update Offset in meta table. Update Offset in meta table.
  • 40. v1 comparison with typical architecture + Pros We have a full transaction log that can be replayed, reworked, saved, fixed Only 1 new tech - kafka Streamer + Leaser = 200 lines of code + 800 lines of tests. It can be used as library not a service Buf/Go/PostgreSQL - everything reused - maintenance simplified. + Pros We have a full transaction log that can be replayed, reworked, saved, fixed Only 1 new tech - kafka Streamer + Leaser = 200 lines of code + 800 lines of tests. It can be used as library not a service Buf/Go/PostgreSQL - everything reused - maintenance simplified. + Pros We have a full transaction log that can be replayed, reworked, saved, fixed Only 1 new tech - kafka Streamer + Leaser = 200 lines of code + 800 lines of tests. It can be used as library not a service Buf/Go/PostgreSQL - everything reused - maintenance simplified. − Cons WAL amplification - 2x. Transactional outbox requires 1 more write to each operation High delay - 2 min for events More CPU load than just reading from WAL.
  • 44. You learned about 2 min delay
  • 45. Orderstreamerdelay: 2min ULIDs - doesn’t allowus to understand the commit orderof events and missing parts. ULIDs - doesn’t allowus to understand the commit orderof events and missing parts. ULIDs - doesn’t allowus to understand the commit orderof events and missing parts.
  • 47. v2 Implementation Auto increment instead of ULIDwill helpyou to report and look formissing IDs. It’s more like “logical time”. Auto increment instead of ULIDwill helpyou to report and look formissing IDs. It’s more like “logical time”. Auto increment instead of ULIDwill helpyou to report and look formissing IDs. It’s more like “logical time”. Look formissing ids for, save them in meta table for2 mins and restream themwhen theyappear. Look formissing ids for, save them in meta table for2 mins and restream themwhen theyappear. Look formissing ids for, save them in meta table for2 mins and restream themwhen theyappear.