Wirewiki.com 운영자가 2억 4천만 개의 도메인 이름에 대해 사실상 즉각적인(P99 0ms) 자동완성을 구현한 기술 사례입니다. 핵심은 키를 누르는 순간(keyDown) 다음 글자까지 예측해 제안을 미리 받아오는(prefetch) 클라이언트 사이드 전략과, 충분히 빠른 API의 조합입니다. 측정 결과 두 번째 키 입력이 끝나기 전에 결과가 준비되면 사용자가 키에서 손을 떼기 전에 결과가 렌더링됩니다.
번역된 본문
곧 별표(asterisk) 이야기로 넘어가겠습니다. 저는 Wirewiki.com을 운영하는데, 도메인 이름 같은 인터넷 인프라를 조회하는 웹사이트입니다. 사람들이 (과거 및 현재의) DNS 레코드, DNS 위임, 이메일 전달 가능성(deliverability) 설정 등을 확인하는 데 도움을 줍니다. 이런 서비스를 제공하는 사이트는 정말 많고(바이브 코딩 덕분에 그 증가 속도가 그 어느 때보다 빨라지고 있죠), 그래서 저는 차별화가 필요했습니다. 제가 선택한 것은 도구의 품질/유용성과 UX였습니다. 자동완성은 Wirewiki를 탐색하는 주요 방법이므로, 최대한 완전하고 정확하며 빨라야 합니다. 즉각적이어야 합니다. 말하자면 다음 프레임에 바로 나올 정도로요. 그리고 대부분 그것을 달성했습니다. 직접 사용해 보세요.
방법은 이렇습니다. keyDown(사용자가 키를 누르기 시작할 때) 시점에, 입력된 글자 + 다음에 올 수 있는 모든 글자에 대한 제안을 미리 받아옵니다(prefetch). 그리고 keyUp(키에서 손을 뗄 때) 시점에 제안을 렌더링합니다.
예를 들어 GET /autocomplete?q=wi 요청은 결과 목록과 함께 "next" 객체를 반환하는데, 여기에는 "wi" 뒤에 "-", ".", 숫자 0-9,字母 a-z가 각각 붙었을 때의 제안 목록이 미리 담겨 있습니다. 예를 들어 "next.k"에는 wikipedia.org, wikimedia.org, wiktionary.org, wikihow.com 등이 포함됩니다.
이렇게 하면 keyPress1Duration + 키 입력 사이 간격 + keyPress2Duration만큼의 시간 예산(time budget)이 생깁니다. API가 두 번째 키 입력이 끝나기 전에 응답하면, 제때 결과를 준비할 수 있습니다. (60Hz 디스플레이는 16.7ms마다 화면을 그립니다. 따라서 p50에서는 기술적으로 8.33ms의 추가 시간 예산이 있지만, p99에서는 거의 0ms입니다.)
그래서 이 글의 목적상 지연 시간(latency)을 'keyUp부터 결과가 렌더링 준비될 때까지'로 정의하겠습니다. P99 0ms라는 말은 99%의 경우, 사용자가 키에서 손을 떼기도 전에 결과가 준비되어 있다는 뜻입니다.
이를 위해 두 가지가 필요합니다: (1) 제안의 클라이언트 사이드 prefetch 및 캐싱, (2) 충분히 빠른 API.
시간 예산은 얼마나 될까요? 이제 두 번의 키 입력 시간과 간격 시간을 쓸 수 있다는 것은 알지만, 밀리초로는 얼마일까요? 제가 도메인 이름 100개를 적당히 빠르게 입력하며 측정한 결과, p99는 121ms였습니다. 여러분도 직접 입력해 보면 자신의 값을 확인할 수 있습니다. 이 측정은 한 번의 키 누름부터 다음 키에서 손을 뗄 때까지의 시간을 재며, 슬라이더를 움직이면 특정 API 지연 시간에서 몇 %의 키 입력이 '다음 프레임' 렌더링이 가능한지 보여줍니다. 121ms — 121ms 지연 시간에서 다음 프레임 렌더링 가능. 수평선 왼쪽 막대는 현재 지연 시간에서 실패하는 입력들입니다.
API를 얼마나 빠르게 만들 수 있을까요? 이제 지연 시간 목표가 121ms라는 것을 알았습니다. 하지만 API를 얼마나 빠르게 만들 수 있을까요? 이 API에는 가장 인기 있는 상위 100만 개 도메인의 Tranco 목록을 사용하고 있습니다. 이 목록이 우선 제안되어야 하고, 현재 사용 중인 다른 도메인 이름들로 보완됩니다. CZDS는 대부분의 gTLD(.com, .net, .org 등)에 대한 전체 도메인 목록을 제공합니다. 아쉽게도 ccTLD(.uk, .de, .fr 등)는 제공되지 않습니다.
We’ll get to the asterisk. I run Wirewiki.com , a website to inspect internet infrastructure like domain names. It helps people check (historic) DNS records, DNS delegation, email deliverability config, etc. There are a ton of sites that offer this (growing faster than ever thanks to vibe coding), so I need a way to stand out. I picked tool quality / usefulness and UX. The autocomplete is the main way to navigate Wirewiki, so it should be as complete, accurate and fast as possible. I want it to be instant . Like, next frame instant. I've mostly achieved that. Try for yourself: replaced with inlined SVGs; leak-guard utilities (text-gray-900 / no-underline) added so page prose CSS cannot tint the white card. Driven by the real command-palette.js below. --> $refs.searchInput.focus())"> DNS root Your IP Tab Cycle tabs Navigate Open Here's how. On keyDown (the user starts pressing a key), we prefetch the suggestions for the typed character + any next character. And on keyUp (the user releases the key), we render the suggestions. GET /autocomplete?q=wi { "results" : [ "wikipedia.org" , "windowsupdate.com" , "windows.net" , "windows.com" , "wixsite.com" , "wikimedia.org" , "wiley.com" , "wildberries.ru" ], "next" : { "-" : [ "wi-fi.ru" , "wi-fi.org" , "wi-fi.click" , "wi-tribe.ph" , "wi-cat.ru" , "wi-fi.link" , "wi-power.com" , "wi-fi.com" ], "." : [ "wi.gov" , "wi.us" , "wi.infomart.co.jp" , "wi.net" , "wi.likebtn.com" , "wi.accountants" , "wi.agency" , "wi.amsterdam" ], "0" : [ "wi0.buzz" , "wi0.com" , "wi0.mobi" , "wi0.site" , "wi0.tech" , "wi0.top" , "wi0.xyz" , "wi00.com" ], … "9" : [ "wi9-h.com" , "wi9.casino" , "wi9.com" , "wi9.lol" , "wi9.mobi" , "wi9.org" , "wi9.top" , "wi9.xyz" ], "a" : [ "wiadomosci.wp.pl" , "wiadomosci.onet.pl" , "wiadomosci.gazeta.pl" , "wialon.com" , "wialon.host" , "wiair.com" , "wiara.pl" , "wiadomosci.radiozet.pl" ], … "k" : [ "wikipedia.org" , "wikimedia.org" , "wiktionary.org" , "wikihow.com" , "wikia.com" , "wikisource.org" , "wikibooks.org" , "wikidot.com" ], … "z" : [ "wizzair.com" , "wizards.com" , "wiz.world" , "wiz.biz" , "wiz.io" , "wiz.cn" , "wizardingworld.com" , "wizaz.pl" ] } } That gives us a time budget of keyPress1Duration + gap between key presses + keyPress2Duration . If the API returns before the end of the second key press, we'll have the results ready in time. (A 60 Hz display renders every 16.7 ms. So we technically have 8.33 ms extra time budget at p50, but near 0 ms at p99.) So for the purpose of this article, we'll define latency as keyUp to results ready for rendering . p99 0 ms means that 99% of the time, the results will be ready before the user even releases the key. We need two things to make this happen: Client side prefetching and caching of the suggestions, and An API that's fast enough. How big is the budget? We now know that we can spend two key press durations and a gap duration, but how long is that in milliseconds? I've measured it while typing 100 domain names reasonably fast and found that p99 works out to 121 ms for me. Here are my results. You can start typing to see what it is for you. Measuring the latency budget This measures the time from one key press to the next release. The slider tells you what % of keystrokes would render next-frame at a given API latency. 121 ms — next-frame at 121 ms latency % next-frame vs. latency. Vertical line = slider. Per-keystroke budget (ms). Bars left of the line miss at the current latency. Type some domains to populate. Reset How fast can we make the API? Okay, so we've got a latency target of 121 ms. But how fast can we make the API? I'm using the Tranco list of the top 1 million most popular domains for this API. These should be suggested first, and supplemented by any other domain name currently in use. CZDS offers the list of all domains for most of the gTLDs (like .com, .net, .org). ccTLDs (like .uk, .de, .fr) are unfortunately not available. But domains for those with any meaningful traffic will be in the Tranco list anyway. There are other sources, like certificate transparency logs and Archive.org that we could use, but I've not integrated them yet. I've designed the API to first search Tranco (the head), and then CZDS (the tail) if necessary. The results are returned in rank order, so the first 8 are the most popular. Head: in-memory character trie. A trie (prefix tree) stores the top 8 suggestions precomputed for every prefix. A prefix lookup is a walk of a few pointers. Worst case time complexity: O(length of what you typed) . Tail: SSD backed memory-mapped block index. The CZDS domains are sorted and delta-compressed into fixed-size blocks with a tiny in-memory directory. A lookup binary-searches the directory (27 MB), then linearly scans one block of 256 names. The 240M domain names take about 2.5 GB of disk space. Hot pages are cached in memory by the OS. Worst case time complexity: O(length of what you typed * log(number of domains)) . Both the number of domains and the query length are bounded. That makes the worst case for both data structures effectively O(1), which should keep p99 latency low. Let's see. I had an LLM stress test the production server. It generated 720k keystroke queries by simulating 60k typed domain names, and replayed them open-loop (firing at a fixed target rate regardless of how fast responses came back). It tested the API in isolation, through Nginx and end-to-end. Load test results Latency percentiles at different request rates. Both axes log-scaled. API only Origin path (nginx + API) End-to-end (Cloudflare + nginx + API) req/s p50 p90 p99 max errors Most requests are answered within 2 ms by the API. Even at 1.6k req/s, Nginx + the API responds in 15 ms 99% of the time. I'm sure we could shave off a couple of milliseconds, but I'm happy with this. Optimizing the API further doesn't make sense, since the network dominates latency. In practice, the autocomplete latency is about equal to the round trip time from the browser through Cloudflare to the server + 10 ms. A round-trip through Cloudflare adds significant latency, but also absorbs frequent requests. In my tests, that end-to-end latency is within our budget. Even when 1000 people are typing at exactly the same time. The problem is that I'm just running a single server in Europe. So traffic from further away will exceed the budget at p99. Traffic from the USA will add 100-200 ms, for example. CDN caching of hot paths and Nielsen's 0.1 s "instantaneous" threshold make up a lot for this, just not enough to make us hit our target. I could set up multiple servers and geo load balance traffic. That would give me the p99 0 ms* latency. But that's a bit much. Even for me. I would do it if I'd make this into a product. I think this is too niche to build a business on, though. But email me if you'd pay for access to this API, I might change my mind. Oh, and this is the bar I set myself for UX on Wirewiki , so if you see anything that could be improved, please let me know as well.