본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

목표는 공평 분배가 아니라 그 불만이야

~12 min · design, wrong-turns, measurement, product-design

Level 0미분류
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

직관적인 해법, 그리고 그게 망가뜨린 것

진단은 분명해졌어. 시끄러운 출처가 머리를 소유하고, 조용한 쪽이 그림을 들고 있다. 뻔한 처방은 라운드로빈이었지. 선반의 모든 출처한테 차례를 줘. 공평하고, 단순하고, 대부분이 딱 그렇게 쓸 거야.

중요한 선반 둘에 대고 재보니까 하나를 고치고 다른 하나를 망가뜨렸어. 굶던 선반은 화면 그림 0 개에서 8 개로 갔지. 멀쩡하던 선반은 12 에서 6 으로 떨어졌고. 균형을 강요한다는 게 기여할 그림이 없는 출처한테 자리를 주고 그림 있는 쪽을 밀어낸다는 뜻이었거든.

구현이 아니라 목적함수가 틀렸어

그걸 튜닝 문제로 다루고 싶어져. 회전에 가중치를 주고, 일부 출처를 면제하고, 규칙을 하나 더 넣고. 아냐. 라운드로빈은 출처 간 균형을 최적화하고 있었는데, 아무도 출처 간 균형을 요청한 적이 없어. 실제 불만은 "내 화면에 그림이 없다"였고.

저 목표들은 선반이 굶고 있을 때만 일치해. 나머지 전부에선 갈라지고, 공평함을 강제하면 이미 진짜 목표를 만족하던 선반을 적극적으로 해쳐. 이건 일반적인 실패 양상이야. 진짜 목표보다 말하기 쉽고, 원칙 있게 들리고, 조건이 바뀌면 아무도 안 원하는 걸 조용히 최적화하는 대리 목표.

불만을 직접 겨눠

고친 규칙은 거의 지나치게 단순하게 읽혀. 보이는 영역에 그림이 이미 충분하면 아무것도 안 해. 아니면 충분해질 때까지 그림 있는 행을 끌어올려. 바닥까지만. 출처 공평함이라는 개념이 그 안 어디에도 없어.

모양을 봐. 개입에 끄는 스위치가 있고 그게 흔한 경우야. 이미 멀쩡한 선반은 완전히 손 안 대. 그러니까 이 장치가 잘 되던 걸 퇴행시킬 수가 없어. 특정 문제가 없으면 아무것도 안 한다는 그 성질이, 겨냥된 수정을 재보지 않은 표면에도 안전하게 배포할 수 있게 해주는 거야.

불평 안 하던 선반을 확인해

과정 교훈이 설계 교훈만큼 중요해. 라운드로빈 버전은 작업을 촉발한 그 선반에서 검증됐고, 거기선 분명한 성공처럼 보였어. 퇴행이 드러난 건 아무도 불평 안 한 두 번째 선반을 같이 쟀기 때문이야.

변경이 공유된 순위나 배치 규칙을 바꿀 때, 그걸 촉발한 선반은 시험 대상으로 제일 정보가 적어. 변경이 거기 맞춰 재단된 케이스니까. 정보는 이미 멀쩡하던 표면에 있어. 수정이 나쁘게만 만들 수 있는 쪽이 거기거든.

불만이 시사한 추상이 아니라 불만 자체를 최적화해. "공평 분배", "균형 잡힌 커버리지", "고른 분포"는 누가 실제로 한 지저분한 말보다 원칙 있게 느껴지는 대리물이고, 진짜 목표가 이미 충족된 모든 경우를 태연히 악화시켜.

Code

틀린 목적함수랑 맞는 목적함수, 그리고 안전하게 만드는 무동작·python
# WRONG TURN: fair shares between sources.
# Measured: starved shelf 0 -> 8 pictures on screen (good), healthy
# shelf 12 -> 6 (bad). It gave slots to sources with no pictures to
# contribute, displacing sources that had them. The implementation was
# fine; the OBJECTIVE was something nobody asked for.
def promote_round_robin(rows, limit):
    by_source = group_by(rows, key=lambda r: r["source_id"])
    out = []
    while len(out) < limit and any(by_source.values()):
        for source_rows in by_source.values():
            if source_rows:
                out.append(source_rows.pop(0))
    return out


# RIGHT: target the complaint -- "my screen has no pictures" -- and
# no-op otherwise. The early return is the whole safety property: a
# shelf that is already fine cannot be made worse by this code.
def apply_image_floor(rows, limit):
    screen = min(HEAD_CARDS, limit)
    have = sum(1 for r in rows[:screen] if r.get("image_url"))
    if have >= IMAGE_FLOOR:
        return rows              # <-- the common case does nothing
    ...                          # promote only as far as the floor

External links

Exercise

특정 불만 때문에 내보낸 수정을 떠올려서, 그때 손이 간 추상이 뭐였는지 짚어봐. 공평함, 균형, 균일성, 일관성. 이어서 그 추상이랑 원래 불만이 다른 답을 낼 케이스를 하나 찾아서, 네 코드가 어느 쪽을 구현하는지 따져봐. 추상 쪽이면 조용히 퇴행한 게 없는지 확인하고.
Hint
갈라지는 케이스는 보통 이미 건강하던 표면이야. 추상은 거기서 뺏어갈 게 있고 불만은 거기서 고칠 게 없거든. 갈라지는 케이스를 못 만들겠으면 추상이 진짜로 동등할 수도 있어. 근데 의심해. 동등하다는 건 보통 불만을 그럴싸한 말로 다시 적었을 뿐이라는 뜻이니까.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.