이 글은 Today I Learn 시리즈의 107번째 기록입니다. (총 125개)

데이터가 같은데 결과가 다르면 원인은 데이터 밖에 있다

메트로놈을 켜면 첫 박만 유독 작게 들렸다. 둘째 박부터는 멀쩡했다.

소리 데이터를 보니 첫 박과 둘째 박은 크기가 똑같이 설정돼 있었다. 같은 값인데 위치만 다르고, 결과가 다르다. 그러면 아무리 데이터를 더 들여다봐도 답이 안 나온다. 원인은 우리가 만든 것 밖에 있다는 뜻이다.

이 판별이 조사 범위를 크게 줄여 줬다. 앱 안을 뒤지는 걸 멈추고 기기 쪽을 보기 시작했다.

무음 뒤 첫 소리는 기기가 눌러 버린다

원인은 콜드스타트 램프업이었다. 기기의 스피커·앰프는 완전한 무음 상태에서 소리가 시작될 때, 퍽 하는 잡음을 막으려고 출력을 0에서 서서히 올린다. 이 서서히 올라가는 구간이 램프업이다.

첫 클릭이 하필 그 구간에 떨어져서 눌린 것이다. 한 번 올라오고 나면 그 뒤는 정상이라 첫 박에서만 나타났다. 앱 코드로 켜고 끌 수 있는 설정이 아니라, 소리가 나가는 경로의 물리적 성질이다.


못 고치는 특성은 없애는 대신 미리 겪게 한다

우리가 못 고치는 것이 원인일 때 선택지는 둘이다. 포기하거나, 그 특성이 우리에게 손해가 안 되는 시점에 미리 일어나게 하는 것이다.

램프업은 소리가 있어야 시작된다. 그래서 진짜 첫 박이 나오기 전에, 사람 귀에 안 들릴 만큼 작은 소리를 0.15초쯤 먼저 흘려보냈다. 이렇게 본 내용 앞에 미리 깔아 두는 구간을 프리롤이라고 부른다. 프리롤이 램프업을 대신 겪어 주고 나면, 첫 박은 이미 올라온 경로로 나가서 안 눌린다.

일반화하면 이렇다. 첫 사용이 느리거나 부실한 시스템에서는, 사용자가 오기 전에 그 첫 사용을 대신 시켜 두면 된다.

방해하는 성질을 그대로 이용해 숨긴다

예열음은 안 들려야 한다. 그런데 완전히 안 들릴 만큼 작으면 램프업을 못 깨울 수도 있다.

여기서 앞의 성질을 거꾸로 썼다. 예열음을 시작은 크게, 끝으로 갈수록 0으로 줄어들게 배치했다. 큰 부분은 아직 경로가 안 올라온 구간에 놓이니 기기가 알아서 눌러 준다. 경로가 다 올라올 무렵엔 예열음이 이미 0이라 첫 박 직전은 조용하다. 방해하던 성질이 그대로 가림막이 됐다.

실제로 최대 볼륨에서 확인해 가며 크기를 낮췄고, 두 번 만에 안 들리는 선을 찾았다. 귀로 확인해야 하는 값은 계산으로 정하지 말고 실기에서 좁히는 게 빠르다.


기준점을 옮기면 그 기준에 매달린 것들을 같이 옮겨야 한다

예열을 넣자 소리는 고쳐졌는데 화면이 어긋났다. 두 번째 박이 울리는데 화면의 불은 첫 박에 들어와 있었다.

화면의 박 표시는 재생 헤드를 기준으로 켜진다. 재생 헤드란 지금까지 실제로 재생된 분량이 어디까지인지 가리키는 위치다. 그런데 이 위치는 재생을 시작한 지점을 0으로 놓고 센다. 앞에 예열을 끼워 넣었으니 실제 클릭은 전부 예열 길이만큼 뒤로 밀렸는데, 세는 쪽 원점은 그대로였다. 그래서 딱 한 박씩 어긋났다.

고치는 방법은 두 가지였다. 원점을 클릭 쪽으로 옮기거나, 어긋난 만큼 화면 점등을 늦추거나. 앞의 것은 재생을 잠깐 멈췄다 되돌리는 조작이 필요했고 그 과정에서 재생이 아예 안 되는 문제가 났다. 그래서 뒤의 것을 택했다 — 점등을 예열 길이만큼 늦춰서 소리와 같은 순간에 뜨게 했다.

교훈은 기술이 아니라 순서다. 무언가를 앞에 끼워 넣을 때는, 그 자리를 기준으로 세고 있던 것이 무엇인지 먼저 찾아야 한다. 소리만 확인하고 끝냈으면 화면이 어긋난 채로 나갈 뻔했다.


부품을 늘리면 기기별 편차에 노출되는 면도 넓어진다

예열을 넣는 길이 셋 있었다. 앞의 둘은 이 기기에서 실패했다.

  • 예열 전용으로 소리 재생기를 하나 더 만들어 돌리는 방법. 첫 박은 잡혔는데 재생할 때마다 재생기를 새로 만드는 것이 간헐적으로 앱을 죽였다.
  • 재생 위치를 0으로 되돌리는 방법. 예열이 다 나올 때까지 기다리는 부분이 끝나지 않아 재생이 시작조차 안 됐다.

결국 예열을 원래 쓰던 재생기 하나 안에 앞구간으로 합치고, 어긋남은 앞의 지연으로 처리했다. 재생기는 여전히 하나고 부수적인 조작도 없다.

부품을 하나 늘리면 기능이 하나 붙는 게 아니라, 기기마다 다르게 동작할 수 있는 면이 하나 더 생긴다. 안드로이드처럼 기기 편차가 큰 바닥에서는 이 비용이 특히 크다. 같은 결과를 낸다면 구조가 단순한 쪽을 고르는 게 안전하다.


사용자가 고를 수 있는 값의 양 끝은 실제로 눌러 봐야 한다

예열을 다 고치고 나서 느린 박자를 시험하다 앱이 꺼졌다. 분당 1박으로 두면 한 마디가 4분이다. 한 마디 분량을 통째로 담으려던 소리 저장 공간이 20MB를 넘었고, 그 크기를 요구하니 재생기가 아예 안 만들어졌다.

정상적인 박자에서는 절대 안 보이는 문제다. 사용자가 고를 수 있는 범위의 끝이라서 아무도 안 눌러 볼 뿐, 못 누르는 값은 아니다.

고친 방식도 판단이 하나 들어 있다. 우리 재생은 데이터를 계속 이어서 흘려보내는 스트리밍 방식이라, 저장 공간이 한 마디를 통째로 담을 이유가 없다. 필요한 만큼이 아니라 최대 몇 초까지라는 상한을 두는 것으로 바꿨다.

값의 범위를 열어 줬으면 그 양 끝은 반드시 눌러 본다. 그리고 크기가 입력에 따라 늘어나는 자리에는 상한을 둔다.


요약

  • 데이터가 같은데 결과가 다르면 원인은 데이터 밖에 있다. 조사할 곳을 줄여 주는 판별이다.
  • 우리가 못 고치는 특성이 원인이면, 없애는 대신 손해가 안 되는 시점에 미리 겪게 해서 우회한다.
  • 방해하는 성질을 그대로 가림막으로 쓸 수 있는지 본다. 새 장치를 더하는 것보다 싸다.
  • 앞에 무언가를 끼워 넣을 때는 그 자리를 기준으로 세던 것을 먼저 찾는다. 기준점을 옮기면 매달린 것도 같이 옮겨야 한다.
  • 부품을 늘리면 기기마다 다르게 동작할 면이 같이 늘어난다. 결과가 같다면 단순한 구조를 고른다.
  • 사용자가 고를 수 있는 값의 양 끝은 실제로 눌러 본다. 입력에 따라 크기가 늘어나는 자리에는 상한을 둔다.

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 insertOnConflictUpdate vs DoUpdate(target) 27 세션 필터 28 아코디언(Accordiaon) UI를 펼친상태로 만들기 29 input의 step 30 Word Cloud 31 Google Sheets를 데이터 버스로(with AppSheet) 32 Django 모델 텍스트 필드 자동 수집 패턴 33 localStorage로 섹션 토글 상태 유지 34 순차 ID 생성(`select_for_update()` + `max()` 조합) 35 역참조 검색과 distinct() 36 xlsx 다운로드와 로딩 오버레이 충돌 37 Android 파일 공유 MIME 타입 38 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 39 UTF-8 BOM과 PowerShell 파일 쓰기 40 소리꽃 KeyBloom TIL 1 41 소리꽃 KeyBloom TIL 2 42 소리꽃 KeyBloom TIL 3 43 메트로놈 Simple Metronome TIL 1 44 소리꽃 KeyBloom TIL 4 45 메트로놈 Simple Metronome TIL 2 46 소리꽃 KeyBloom TIL 5 47 메트로놈 Simple Metronome TIL 3 48 정적 블로그 SEO 정비와 Pagefind 검색 도입 49 메트로놈 Simple Metronome TIL 4 50 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 51 메트로놈 Simple Metronome TIL 5 52 모래게임 Sandrop TIL 1 53 메트로놈 Simple Metronome TIL 6 54 모래게임 Sandrop TIL 2 55 모래게임 Sandrop TIL 3 56 메트로놈 Simple Metronome TIL 7 57 모래게임 Sandrop TIL 4 58 모래게임 Sandrop TIL 5 59 모래게임 Sandrop TIL 6 60 소리꽃 KeyBloom TIL 6 61 모래게임 Sandrop TIL 7 62 모래게임 Sandrop TIL 8 63 소리꽃 KeyBloom TIL 7 64 모래게임 Sandrop TIL 9 65 소리꽃 KeyBloom TIL 8 66 모래게임 Sandrop TIL 10 67 소리꽃 KeyBloom TIL 9 68 소리꽃 KeyBloom TIL 10 69 소리꽃 KeyBloom TIL 11 70 소리꽃 KeyBloom TIL 12 71 소리꽃 KeyBloom TIL 13 72 온실 GreenHouse TIL 1 73 소리꽃 KeyBloom TIL 14 74 온실 GreenHouse TIL 2 75 소리꽃 KeyBloom TIL 15 76 소리꽃 KeyBloom TIL 16 77 소리꽃 KeyBloom TIL 17 78 소리꽃 KeyBloom TIL 18 79 소리꽃 KeyBloom TIL 19 80 소리꽃 KeyBloom TIL 20 81 로그스톤 상표 셀프 출원 TIL 1 82 소리꽃 KeyBloom TIL 21 83 소리꽃 KeyBloom TIL 22 84 소리꽃 KeyBloom TIL 23 85 소리꽃 KeyBloom TIL 24 86 MiniMacro TIL 1 87 Astro가 무엇인지, 왜 옮기는지 88 MiniMacro TIL 2 89 메트로놈 Simple Metronome TIL 8 90 메트로놈 Simple Metronome TIL 9 91 블로그 Astro 이관 TIL 92 오픈데이 Openday TIL 1 93 오픈데이 Openday TIL 2 94 Unreal Engine MCP TIL 1 95 콘티온 Conti On TIL 11 96 오픈데이 Openday TIL 3 97 오픈데이 Openday TIL 4 98 오픈데이 Openday TIL 5 99 ScorePlayer TIL 1 100 BuildingHub TIL 1 101 ScorePlayer TIL 2 102 BuildingHub TIL 2 103 ScorePlayer TIL 3 104 Unreal Engine MCP TIL 2 105 궁 미로 PalaceMaze TIL 1 106 메트로놈 Simple Metronome TIL 10 107 메트로놈 Simple Metronome TIL 11 읽는 중 108 오픈데이 Openday TIL 6 109 BuildingHub TIL 3 110 Obsidian vault에서 코드만 빼기 — directory junction 111 ScorePlayer TIL 4 112 오픈데이 Openday TIL 7 113 URL은 바뀔 수 있는 것에 묶지 않는다 114 도구는 기능이 아니라 내 일에 맞는지로 고른다 115 돌이키기 힘든 변경은 작게 먼저 확인한다 116 궁 미로 PalaceMaze TIL 3 117 궁 미로 PalaceMaze TIL 2 118 소리꽃 KeyBloom TIL 29 119 오픈데이 Openday TIL 8 120 BuildingHub TIL 4 121 ScorePlayer TIL 5 122 BuildingHub TIL 5 123 MiniMacro TIL 3 124 소리꽃 KeyBloom TIL 30 125 궁 미로 PalaceMaze TIL 4