색인 리스트가 있는 지수 회피

26769 단어 functional
최근에 우리의 생산은 침체 상태에 빠졌다.세 개의 상호 의존적인 AWS Lambda 함수는 RDS 데이터 API에서 단일 DB 열이 너무 커서 다운로드할 수 없습니다.그들 모두exponential backoff가 없기 때문에 그들은 기본적으로 모든 다른 서비스의 DB를 동결했다. 그리고 이 서비스들은 즉시 그들의 관련 서비스에 반복적으로 요청을 했다. 이로 인해 창고는 당밀의 속도로 약 30미터를 운행했고 엔트로피 시스템이 멈추었다.
서버가 없는 체계 구조에서 지수의 회피는 큰일이다. 특히 RDS 데이터 API 등 예측할 수 없는 까다로운 병목이 존재할 때 말이다.그래서 나는 오늘 아침부터 이 문제를 해결하기 시작했고 마지막으로 색인 리스트를 사용하여 여러 서비스에 걸쳐 이 문제를 해결했다.본고에서, 나는 색인 리스트가 무엇인지 간략하게 소개한 다음에, 그것들이 어떻게 우아하고 간결하게 지수 반환 등 복잡한 문제를 해결하는데 도움을 줄 수 있는지 소개할 것이다.

색인 목록
색인 리스트는 입력 형식i과 출력 형식o이 있는 리스트입니다.이 두 가지 유형은 모두 허형이다. 구체적인 값에 맞지 않고, 다른 작업 다음에 어떤 작업을 수행할 수 있는지 유형 단계에 표시하는 데 사용된다.Justin Wooa great article는 색인 목록을 사용해서 Pure Script에 햄버거를 구축하는 방법에 관한 글을 썼다.첫 번째 '입력 형식' 은 밑에 있는 bun에 대응하고, 첫 번째 출력 형식은fixings입니다.이렇게 수행됩니다.i o밑빵
이 가능하다, ~할 수 있다,...
고정 장치
더 많은 고정이 있을 수 있어요.
더 많은 고정이 있을 수 있어요.
고기 부스러기
고기 부스러기
치즈일 수도 있어요.
치즈일 수도 있어요.
빵집
색인 목록을 사용하지 않으면 Burger BottomBun, Burger (List Fixings) 등 형식을 사용할 수 있지만, 이 내용을 강제로 실행할 수 없습니다. order색인 목록은 목록의 귀속 순서를 강제로 실행할 수 있습니다.순서는 네가 원하는 대로 할 수 있다. - 시작, 조건, 등등으로 순환할 수 있다.

색인 규칙 적용
Justin의 예에서는 함수 서명을 사용하여 색인 규칙을 적용합니다.
placeEmptyBun :: BurgerSpec -> IxBurgerBuilder EmptyPlate BottomBunOn BurgerSpec
placeEmptyBun = addIngredient "Bottom Bun"
이런 기능을 구축하면 아주 좋은 DSL을 만들 수 있다. 만약에 한 사람이 빈 접시로 하나를 따라가려고 한다면, 그것은 실패할 것이다. 가장 야만적이고 저속한 햄버거 식자만이 이렇게 할 수 있다.그러나 야만적인 햄버거 식객 중 한 명이 나타나 새로운 함수 BottomBun 를 작성할 수도 있다.
eatEmptyBun :: BurgerSpec -> IxBurgerBuilder BottomBunOn EmptyPlate BurgerSpec
eatEmptyBun = mempty
더 일반적으로, 함수 서명을 강제로 실행할 수 있는 작성 방식이 없기 때문에, 누구나 원하는 버거 building 함수를 작성할 수 있다.

서로 등을 맞대다
나의 지수 회피 예시에서 나는 이런 상황이 나타나기를 원하지 않는다.나는 유형 시스템을 통해 다음 요청을 강제로 실행하는 데 항상 이전 요청보다 더 많은 시간이 걸릴 것이라고 생각한다.eatEmptyBuni 유형의 표를 다시 봅시다.o i기다리다
조금만 더 기다려
조금만 더 기다려
조금 더 기다리다
조금 더 기다리다
잠깐 기다리다
잠깐 기다리다
걷어치우다
나는 아무도 가고 싶지 않다o.이로 인해 AWS가 다시 붕괴될 수 있습니다.

구원을 위해 노력하다
TypeClass는 컴파일러에 존재하는 표일 뿐입니다. 열은 제약할 형식이고 줄은 제약입니다.따라서 우리는 typeclass를 사용하여 Wait a small eternity -> Wait a bit -> Wait a bit -> Wait a bit -> Wait a bit -> Give upi 유형을 구속할 수 있다. 이것은 당연한 것이다.따라서 만약에 우리의 색인 목록이 o이라면 우리의 유형 클래스는 다음과 같다.
class ExponentialBackoff i t where
  continue :: forall m a. m i i a -> m i t a

instance exponentialBackoff0 :: WaitABit GiveUp
  continue :: ...
instance exponentialBackoff1 :: WaitABitLonger GiveUp 
instance exponentialBackoff2 :: WaitEvenLonger GiveUp 
여기에 많은 샘플 파일이 있기 때문에 나는 Program i o a 로 실제 코드 라이브러리의 귀속 관계를 표시하기로 결정했다.
class ExponentialBackoff i  where
  continue :: forall m a. m i i a -> m i Z a

instance exponentialBackoff0 :: Z
  continue :: ...
instance exponentialBackoff1 :: (Succ i)
purescript-typelevel-peano 함수는 continue 블록의 끝에 나타난다. 이것은 우리의 프로그램이 하나의 유형I.do으로 실행을 마치면 m (Succ i) (Succ i) a는 우리가 진행하고 있는 반환 절차이다. 즉, (Succ i) (Succ i)(Succ (Succ Z)) 우리의 peano 카운트다운에서 2까지 떨어진다는 것을 의미한다. m i i aiZ(0)이다.이 때 continue에 실행Z됩니다.우리의 코드 라이브러리에서, 이것은 프로그램의 종료와 우아한 청소이다.
정수의 typelevel을 사용하여 나타내는 또 다른 장점은 reflectNat 를 사용하여 유형에서 지수의 회피 값을 직접 계산할 수 있다는 것이다.

전체 예
비록 우리의 백엔드 코드 라이브러리 (불행하게도) 는 아직 시작되지 않았지만, 우리는 생산 과정에서 이 물건들을 어떻게 사용하는지 예시를 제공하기 위해 S3Squirrel 라는 소형 ish 서비스를 사용해 왔다.Google 서비스에서, Google은 backoff 유형 클래스의 Backoff 함수를 사용하여 1원 상하문에서 정확한 지수 반환을 얻습니다.우리가 이 과정을 추진한 이후로, 우리는 AWS 융해 사건이 발생하지 않았다.w00t!💯
data S3SquirrelProgramF a
  = GetETagForResource String (Maybe String -> a)
  | GetS3InfoFromDBOnCacheHit String (Maybe String) (Maybe S3Info -> a)
  | DownloadResourceToFile String String (Unit -> a)
  | ReadFileToBuffer String (Buffer -> a)
  | GenerateUUID (String -> a)
  | FreeDelay Number (Unit -> a)
  | UploadObjectToS3 String String Buffer (Unit -> a)
  | WriteEtagAndS3InfoToDb String (Maybe String) String String (Either Error Unit -> a)
  | FreeLog String (Unit -> a)
  | FreeThrow Error (Unit -> a)

derive instance functorS3SquirrelProgramF :: Functor S3SquirrelProgramF

derive instance genericS3SquirrelProgramF :: Generic (S3SquirrelProgramF a) _

f :: forall i. Constructors S3SquirrelProgramF (S3SquirrelProgram i i)
f = constructors (wrap <<< liftF :: S3SquirrelProgramF ~> (S3SquirrelProgram i i))

newtype S3SquirrelProgram i o a
  = S3SquirrelProgram (Free S3SquirrelProgramF a)

derive instance newtypeS3SquirrelProgram :: Newtype (S3SquirrelProgram i o a) _

instance ixApplicativeS3SquirrelProgram :: IxApplicative S3SquirrelProgram where
  ipure = wrap <<< pure

instance ixFunctorS3SquirrelProgram :: IxFunctor S3SquirrelProgram where
  imap fx (S3SquirrelProgram x) = S3SquirrelProgram (map fx x)

instance ixApplyS3SquirrelProgram :: IxApply S3SquirrelProgram where
  iapply (S3SquirrelProgram fx) (S3SquirrelProgram x) = S3SquirrelProgram (apply fx x)

instance ixBindS3SquirrelProgram :: IxBind S3SquirrelProgram where
  ibind (S3SquirrelProgram x) fx = wrap (x >>= (unwrap <<< fx))

instance ixMonadS3SquirrelProgram :: IxMonad S3SquirrelProgram

type SProg (x :: Nat) (y :: Nat)
  = S3SquirrelProgram (NProxy x) (NProxy y) Unit

class ProgramOutcome (i :: Nat) where
  s3SquirrelNext :: Error -> ResourceInfo -> SProg i Z

class MaxRetries t (i :: Nat) | t -> i

instance maxRetriesS3SquirrelProgram :: MaxRetries (S3SquirrelProgram i i a) (Succ (Succ (Succ Z)))

class Backoff m where
  backoff :: (Number -> m Unit) -> m Unit

instance backoffS3SquirrelProgram :: (IsNat x, IsNat i, MaxRetries (S3SquirrelProgram (NProxy i) (NProxy i) a) x) => Backoff (S3SquirrelProgram (NProxy i) (NProxy i)) where
  backoff = (#) (2.0 `pow` (toNumber (maxRetries - currentPos)))
    where
    maxRetries = reflectNat (NProxy :: NProxy x)

    currentPos = reflectNat (NProxy :: NProxy i)

instance programOutcomeZ :: ProgramOutcome Z where
  s3SquirrelNext e _ = f.freeThrow e

instance programOutcomeSucc :: (IsNat x, ProgramOutcome x) => ProgramOutcome (Succ x) where
  s3SquirrelNext e ri =
    ( Ix.do
        S3SquirrelProgram $ pure unit :: SProg (Succ x) x
        s3SquirrelProgram ri :: SProg x Z
    )

downloadAndWriteToDb :: forall (i :: Nat). IsNat i => ProgramOutcome i => String -> Maybe String -> SProg i Z
downloadAndWriteToDb resourceUrl etag = Ix.do
  uuid <- f.generateUUID
  let
    filename = "/tmp/" <> uuid
  f.downloadResourceToFile resourceUrl filename
  buffer <- f.readFileToBuffer filename
  f.uploadObjectToS3 mknBucket uuid buffer
  f.freeLog ("writing to db " <> resourceUrl <> " etag: " <> show etag)
  res <-
    f.writeEtagAndS3InfoToDb
      resourceUrl
      etag
      mknBucket
      uuid
  case res of
    Left err -> Ix.do
      f.freeLog (show err)
      backoff f.freeDelay
      s3SquirrelNext err { resourceUrl }
    Right _ -> S3SquirrelProgram (pure unit)

s3SquirrelProgram :: forall (i :: Nat). IsNat i => ProgramOutcome i => ResourceInfo -> SProg i Z
s3SquirrelProgram { resourceUrl } = Ix.do
  etag <- f.getETagForResource resourceUrl
  f.freeLog ("got etag " <> show etag <> " for resource " <> resourceUrl)
  s3Info' <- f.getS3InfoFromDBOnCacheHit resourceUrl etag
  f.freeLog ("got s3info " <> show s3Info')
  case s3Info' of
    Nothing -> downloadAndWriteToDb resourceUrl etag
    Just { bucket, key } -> S3SquirrelProgram (pure unit)

좋은 웹페이지 즐겨찾기