목표 경험치가 사라졌고, 돌려드릴 방법이 없었습니다
전혀 다른 일을 준비하다가 발견했습니다. 목표를 달성하면 쌓이는 경험치와 배지가 브라우저 안에만 저장되고 있었어요.
목표 자체는 서버에 있습니다. 그래서 기기를 바꿔도 목표는 그대로 따라옵니다. 따라오지 않는 건 그 목표로 모은 경험치와 배지뿐이에요. 사용자 입장에서는 이게 더 나쁩니다. 전부 사라졌으면 «로그인이 잘못됐나» 싶을 텐데, 목표는 멀쩡히 있는데 성취만 비어 있으면 내 것만 골라 지워진 것처럼 보이니까요.
몇 달 동안 아무도 몰랐습니다. 그리고 알았을 때는 돌려드릴 방법이 없었어요. 서버에 없던 값은 어디에도 없습니다.
누가 게을러서 생긴 일은 아니었습니다. 화면을 만들다 브라우저에 값 하나 저장하는 데는 아무 비용도 들지 않아요. 코드 한 줄이고, 곧바로 동작하고, 아무도 검토하지 않습니다. 그래서 그게 결정이라는 느낌 자체가 없습니다. 서버에 저장하려면 스키마를 정하고 API를 만들고 마이그레이션을 쓰는 동안 최소한 «이걸 왜 저장하지»를 한 번은 생각하게 되는데, 브라우저에 쓸 때는 그 순간이 없어요.
그래서 목록을 만들었습니다
규범으로는 안 막힙니다. 「중요한 값은 서버에 저장합시다」를 문서에 적어 두면 그 문서를 읽은 사람만, 그것도 기억하는 동안만 지켜져요.
대신 등록을 강제했습니다. 브라우저에 뭔가를 저장하는 파일이 새로 생기면 검사가 목록 등재를 요구하고, 등재하려면 세 칸 중 하나를 골라야 합니다.
| 칸 | 뜻 |
|---|---|
| 기기 | 이 기기에서만 의미가 있다. 사라져도 손해가 없다 — 접었다 편 상태, 닫음 표시, 마지막에 본 탭 같은 것 |
| 계정 | 계정을 따라가야 한다. 이미 서버에도 있다 |
| 미이관 | 따라가야 하는데 아직 안 옮겼다. 사유를 반드시 적는다 |
고르는 순간이 곧 결정이고, 그 결정이 파일에 남습니다. 나중에 «이건 왜 브라우저에만 있지»라는 질문이 나오면 답이 이미 적혀 있어요.
지금 목록에는 파일 60개가 올라와 있습니다. 계정 9개, 기기 49개, 미이관 2개.
미이관 칸은 빚을 이름 붙여 두는 자리입니다. 비어 있는 것이 정상이고, 채워져 있으면 그만큼이 «지금 기기를 바꾸면 사라지는 것»이에요. 처음 목록을 채웠을 때는 5개였습니다. 다시 만들 수 없는 것부터 옮겼고 — 직접 정한 카테고리 색과 메일 그룹 프리셋이 그랬어요 — 남은 두 개는 인맥 지도에서 손으로 옮긴 노드 위치와 탭 연동 기능의 켜고 끔입니다. 둘 다 사라져도 다시 만들 수 있는 값이라 뒤로 미뤘습니다.
서버로 옮기는 것만으로는 부족했습니다
여기서 한 번 크게 잘못 짚었습니다. «서버에 올리면 끝»인 줄 알았어요.
기기가 둘이면 각자 다른 값을 밀어 올립니다. 폰에서 목표를 달성해 경험치가 오르고, PC에는 어제 값이 남아 있어요. 마지막에 쓴 쪽이 이기게 두면 PC를 켜는 순간 폰에서 딴 경험치가 옛 값에 덮여 사라집니다. 처음 잃은 것과 똑같은 일이 다른 경로로 다시 일어나는 거예요.
그래서 올리는 동작이 저장이 아니라 병합입니다. 그리고 병합 규칙은 서버 한 곳만 갖습니다. 기기마다 각자 합치면 두 기기가 서로 다른 결과에 도달해 영영 수렴하지 않거든요.
규칙은 값의 성질을 따릅니다.
| 값의 성질 | 합치는 방법 |
|---|---|
| 되돌릴 일이 없는 기록 (딴 배지, 완료한 안내) | 양쪽을 모두 남긴다 |
| 사용자가 껐다 켤 수 있는 선택 (설정, 직접 만든 목록) | 마지막으로 손댄 쪽을 통째로 |
| 수치 (경험치, 누적 횟수) | 큰 쪽 |
이 선택이 생각보다 무겁습니다. 잘못 고르면 아무 오류 없이 사용자만 손해를 봅니다. 예를 들어 고정해 둔 배지를 «양쪽 모두 남긴다»로 처리하면, 사용자가 배지를 빼도 서버가 친절하게 다시 합쳐 줍니다. 영영 못 빼는 거예요. 반대로 누적 경험치를 더하는 방식으로 합치면 두 기기가 같은 목표로 적립했을 때 점수가 두 배가 됩니다.
실제로 한 번 틀렸습니다
안내 투어에 「처음부터 다시 보기」 버튼이 있습니다. 눌러도 아무 일이 일어나지 않는다는 걸 나중에 발견했어요.
정확히는 일어나긴 했습니다. 화면은 초기화됐다고 보여 줍니다. 그리고 3초쯤 뒤에 서버가 원래대로 돌려놓습니다.
완료한 투어 기록을 «양쪽을 모두 남긴다»로 합치고 있었기 때문입니다. 기기마다 각자 본 투어를 잃지 않으려고 그렇게 정한 규칙이었는데, 되돌리기는 «이 기록을 없앤다»는 뜻이라 그 규칙 아래서는 지운 자리를 서버에 남아 있던 옛 기록이 그대로 메웁니다. 사용자에게는 버튼이 고장 난 것으로 보이고요.
고친 방법은 되돌린 시각을 함께 보내는 것이었습니다. 그 시각이 더 늦은 쪽의 상태를 통째로 새 기준으로 인정하면, 되돌리기가 합치기에 흡수되지 않아요.
배운 건 이겁니다. 병합 규칙은 기술 설정이 아니라 «이 값은 어떤 성질의 것인가»에 대한 판단입니다. 판단을 안 하고 기본값을 쓰면, 그 기본값이 대신 판단해 버립니다.
목록에도 구멍이 있습니다
이 목록은 파일 단위입니다. «이 파일이 브라우저에 값을 쓰는가»는 답할 수 있지만 «언제 서버로 가는가»는 못 봐요.
홈 화면 꾸미기 스티커가 그렇습니다. 목록에는 «계정»으로 올라 있고 실제로 서버에도 저장됩니다. 그런데 서버로 가는 시점이 「꾸미기 저장」을 눌렀을 때뿐이에요. 스티커를 옮겨 놓고 저장 대신 취소로 빠져나가면 그 배치는 이 기기에만 남습니다. 목록은 이 파일을 «계정»이라고 말하는데, 어떤 경로로는 아닌 겁니다.
당장 고치지는 않았습니다. 스티커를 옮길 때마다 서버에 올리면 드래그 한 번에 수십 번을 밀어 올리게 되니까요. 대신 목록에 «알려진 구멍»으로 적어 뒀습니다. 고치지 못한 것을 안 보이게 두는 것보다, 이름을 붙여 눈에 띄게 두는 편이 낫다고 봤어요.
브라우저에 값 하나 저장하는 비용은 여전히 0입니다. 달라진 건 그 한 줄을 쓰려면 목록에 이름을 올려야 하고, 올리려면 세 칸 중 하나를 골라야 한다는 것뿐이에요. 고르는 데 30초쯤 걸립니다.
사라진 경험치는 아직도 돌려드리지 못했습니다. 그 뒤에 만든 건 전부 다음 사람을 위한 것입니다.