CINXE.COM
ATProto for distributed systems engineers - AT Protocol
<!DOCTYPE html><html lang="en" class="h-full"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/786ca9324488b5df.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-496a37b6c26ccd6a.js"/><script src="/_next/static/chunks/fd9d1056-ae1d3d1d803e2e5a.js" async=""></script><script src="/_next/static/chunks/117-545f1187d7f142b2.js" async=""></script><script src="/_next/static/chunks/main-app-3122dc718821276f.js" async=""></script><script src="/_next/static/chunks/972-f3553dcbba031b91.js" async=""></script><script src="/_next/static/chunks/135-8f28b0a70a96c388.js" async=""></script><script src="/_next/static/chunks/28-313384c6289a00c0.js" async=""></script><script src="/_next/static/chunks/212-67e821188dcd5fb8.js" async=""></script><script src="/_next/static/chunks/app/%5Blocale%5D/articles/atproto-for-distsys-engineers/page-3c97c5d3e0a96ea7.js" async=""></script><script src="/_next/static/chunks/369-a583bbb81b96a7d8.js" async=""></script><script src="/_next/static/chunks/60-3e1824554a9cf3f8.js" async=""></script><script src="/_next/static/chunks/app/%5Blocale%5D/layout-8706067c5d993875.js" async=""></script><title>ATProto for distributed systems engineers - AT Protocol</title><meta name="description" content="AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering."/><meta property="og:title" content="ATProto for distributed systems engineers - AT Protocol"/><meta property="og:description" content="AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering."/><meta property="og:url" content="https://atproto.com/"/><meta property="og:site_name" content="AT Protocol"/><meta property="og:image" content="https://atproto.com/default-social-card.png"/><meta property="og:image:secure_url" content="https://atproto.com/default-social-card.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="630"/><meta property="og:type" content="website"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:title" content="ATProto for distributed systems engineers - AT Protocol"/><meta name="twitter:description" content="AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering."/><meta name="twitter:image" content="https://atproto.com/default-social-card.png"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="flex min-h-full bg-white antialiased dark:bg-zinc-900"><script>!function(){try{var d=document.documentElement,c=d.classList;c.remove('light','dark');var e=localStorage.getItem('theme');if('system'===e||(!e&&true)){var t='(prefers-color-scheme: dark)',m=window.matchMedia(t);if(m.media!==t||m.matches){d.style.colorScheme = 'dark';c.add('dark')}else{d.style.colorScheme = 'light';c.add('light')}}else if(e){c.add(e|| '')}if(e==='light'||e==='dark')d.style.colorScheme=e}catch(e){}}()</script><div class="w-full"><div class="h-full lg:ml-72 xl:ml-80"><header class="contents lg:pointer-events-none lg:fixed lg:inset-0 lg:z-40 lg:flex"><div class="contents lg:pointer-events-auto lg:block lg:w-72 lg:overflow-y-auto lg:border-r lg:border-zinc-900/10 lg:px-6 lg:pb-8 lg:pt-4 xl:w-80 lg:dark:border-white/10"><div class="hidden lg:flex"><a aria-label="Home" href="/"><svg viewBox="0 0 162 28" aria-hidden="true" class="h-6"><path class="fill-blue-500" d="M14.362 27.78c-1.956 0-3.756-.324-5.4-.972-1.644-.648-3.072-1.566-4.284-2.754a12.247 12.247 0 0 1-2.808-4.158c-.66-1.596-.99-3.33-.99-5.202 0-2.232.342-4.212 1.026-5.94.696-1.728 1.656-3.18 2.88-4.356a12.48 12.48 0 0 1 4.284-2.7c1.632-.612 3.378-.918 5.238-.918 2.28 0 4.278.354 5.994 1.062 1.716.708 3.144 1.668 4.284 2.88a11.706 11.706 0 0 1 2.538 4.158c.552 1.548.804 3.168.756 4.86-.06 2.328-.546 4.116-1.458 5.364-.912 1.236-2.328 1.854-4.248 1.854a5.839 5.839 0 0 1-2.826-.702 3.703 3.703 0 0 1-1.764-2.07l1.044.054c-.492.924-1.164 1.572-2.016 1.944a6.464 6.464 0 0 1-2.61.558c-1.212 0-2.28-.258-3.204-.774a5.682 5.682 0 0 1-2.178-2.214c-.528-.948-.792-2.046-.792-3.294 0-1.284.276-2.394.828-3.33a5.77 5.77 0 0 1 2.232-2.196c.936-.516 1.992-.774 3.168-.774.78 0 1.59.162 2.43.486.852.324 1.512.78 1.98 1.368l-.738.936V8.664h2.412l-.054 6.462c0 .924.18 1.62.54 2.088.36.468.894.702 1.602.702.624 0 1.104-.174 1.44-.522.348-.36.588-.846.72-1.458a10.66 10.66 0 0 0 .252-2.106c.036-1.86-.24-3.426-.828-4.698-.588-1.272-1.386-2.298-2.394-3.078a9.499 9.499 0 0 0-3.294-1.71c-1.2-.36-2.394-.54-3.582-.54-1.68 0-3.174.27-4.482.81-1.308.528-2.412 1.278-3.312 2.25-.888.96-1.56 2.1-2.016 3.42-.444 1.308-.654 2.748-.63 4.32.048 1.56.33 2.964.846 4.212a9.324 9.324 0 0 0 2.16 3.204 9.38 9.38 0 0 0 3.276 2.034c1.26.468 2.64.702 4.14.702.84 0 1.674-.096 2.502-.288.84-.18 1.608-.438 2.304-.774l1.026 2.808c-.924.432-1.896.75-2.916.954a14.649 14.649 0 0 1-3.078.324Zm-.144-10.098c.852 0 1.566-.246 2.142-.738.576-.492.864-1.326.864-2.502 0-1.068-.258-1.872-.774-2.412-.504-.552-1.218-.828-2.142-.828-1.092 0-1.908.288-2.448.864-.54.576-.81 1.368-.81 2.376 0 1.032.276 1.83.828 2.394.564.564 1.344.846 2.34.846Z"></path><path class="fill-blue-600 dark:fill-blue-500" d="M51.799 7.813V5.545h13.509v2.268H59.86V23h-2.624V7.812h-5.438ZM39.392 23h-2.795l6.28-17.455h3.043L52.203 23h-2.796L44.472 8.716h-.137L39.392 23Zm.469-6.835h9.068v2.216h-9.068v-2.216Z"></path><path class="fill-zinc-700 dark:fill-zinc-400" d="M161.144 5.545V23h-2.548V5.545h2.548ZM149.649 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.506 1.017.758 2.204.758 3.562 0 1.352-.252 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.384-1.468.384-2.31 0-.834-.128-1.602-.384-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.801 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.194.631 1.995.631ZM136.032 23.264c-1.267 0-2.358-.287-3.273-.86-.909-.58-1.608-1.378-2.096-2.395-.489-1.018-.733-2.182-.733-3.495 0-1.33.25-2.503.75-3.52.5-1.022 1.204-1.82 2.113-2.395.909-.573 1.98-.86 3.213-.86.995 0 1.881.184 2.659.554a4.764 4.764 0 0 1 1.884 1.534c.483.659.77 1.429.861 2.31h-2.48a2.974 2.974 0 0 0-.938-1.586c-.483-.443-1.13-.665-1.943-.665-.71 0-1.332.188-1.866.563-.529.37-.941.898-1.236 1.585-.296.682-.443 1.489-.443 2.42 0 .955.144 1.779.434 2.472.29.693.699 1.23 1.227 1.61.535.382 1.162.572 1.884.572.483 0 .92-.088 1.313-.264.397-.182.73-.44.997-.776.272-.335.463-.739.571-1.21h2.48a4.823 4.823 0 0 1-.827 2.267 4.76 4.76 0 0 1-1.849 1.568c-.767.38-1.668.571-2.702.571ZM121.571 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.802 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM113.379 9.91v2.044h-7.151V9.91h7.151Zm-5.233-3.137h2.548v12.383c0 .495.074.867.221 1.117.148.244.339.412.572.503.238.085.497.127.775.127.205 0 .384-.014.537-.042l.358-.068.46 2.105a4.307 4.307 0 0 1-.63.17 4.992 4.992 0 0 1-1.023.102 4.483 4.483 0 0 1-1.875-.358 3.208 3.208 0 0 1-1.406-1.159c-.358-.522-.537-1.179-.537-1.968V6.773ZM98.321 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.13-2.361-.506-1.012-.76-2.194-.76-3.546 0-1.358.254-2.545.76-3.562.505-1.017 1.215-1.807 2.13-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.008-2.139c.796 0 1.455-.21 1.978-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.978-.639-.8 0-1.465.213-1.994.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.644 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM84.065 23V9.91h2.463v2.079h.136a3.164 3.164 0 0 1 1.261-1.662 3.61 3.61 0 0 1 2.063-.614 10.896 10.896 0 0 1 1.082.06v2.437a4.577 4.577 0 0 0-.545-.094 5.202 5.202 0 0 0-.784-.06c-.603 0-1.14.129-1.611.384a2.85 2.85 0 0 0-1.517 2.566V23h-2.548ZM68.918 23V5.545h6.221c1.358 0 2.483.248 3.375.742.892.494 1.56 1.17 2.003 2.028.443.853.665 1.813.665 2.881 0 1.074-.225 2.04-.674 2.898-.443.852-1.113 1.528-2.01 2.028-.893.494-2.015.742-3.367.742h-4.279V14.63h4.04c.858 0 1.554-.148 2.088-.444.534-.3.926-.71 1.176-1.227.25-.517.375-1.105.375-1.764 0-.66-.125-1.244-.375-1.756-.25-.511-.645-.912-1.184-1.201-.534-.29-1.239-.435-2.114-.435h-3.307V23h-2.633Z"></path></svg></a></div><div class="fixed inset-x-0 top-0 z-50 flex h-14 items-center justify-between gap-12 px-4 transition sm:px-6 lg:left-72 lg:z-30 lg:px-8 xl:left-80 backdrop-blur-sm lg:left-72 xl:left-80 dark:backdrop-blur bg-white/[var(--bg-opacity-light)] dark:bg-zinc-900/[var(--bg-opacity-dark)]" style="--bg-opacity-light:0.5;--bg-opacity-dark:0.2"><div class="absolute inset-x-0 top-full h-px transition bg-zinc-900/7.5 dark:bg-white/7.5"></div><div class="hidden lg:block lg:max-w-md lg:flex-auto"><button type="button" class="hidden h-8 w-full items-center gap-2 rounded-full bg-white pl-2 pr-3 text-sm text-zinc-500 ring-1 ring-zinc-900/10 transition hover:ring-zinc-900/20 ui-not-focus-visible:outline-none lg:flex dark:bg-white/5 dark:text-zinc-400 dark:ring-inset dark:ring-white/10 dark:hover:ring-white/20"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5 stroke-current"><path stroke-linecap="round" stroke-linejoin="round" d="M12.01 12a4.25 4.25 0 1 0-6.02-6 4.25 4.25 0 0 0 6.02 6Zm0 0 3.24 3.25"></path></svg>Find something...<kbd class="ml-auto text-2xs text-zinc-400 dark:text-zinc-500"><kbd class="font-sans"></kbd><kbd class="font-sans">K</kbd></kbd></button><!--$--><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span><!--/$--></div><div class="flex items-center gap-5 lg:hidden"><button type="button" class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5" aria-label="Toggle navigation"><svg viewBox="0 0 10 9" fill="none" stroke-linecap="round" aria-hidden="true" class="w-2.5 stroke-zinc-900 dark:stroke-white"><path d="M.5 1h9M.5 8h9M.5 4.5h9"></path></svg></button><!--$--><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span><!--/$--><a aria-label="Home" href="/"><svg viewBox="0 0 162 28" aria-hidden="true" class="h-6"><path class="fill-blue-500" d="M14.362 27.78c-1.956 0-3.756-.324-5.4-.972-1.644-.648-3.072-1.566-4.284-2.754a12.247 12.247 0 0 1-2.808-4.158c-.66-1.596-.99-3.33-.99-5.202 0-2.232.342-4.212 1.026-5.94.696-1.728 1.656-3.18 2.88-4.356a12.48 12.48 0 0 1 4.284-2.7c1.632-.612 3.378-.918 5.238-.918 2.28 0 4.278.354 5.994 1.062 1.716.708 3.144 1.668 4.284 2.88a11.706 11.706 0 0 1 2.538 4.158c.552 1.548.804 3.168.756 4.86-.06 2.328-.546 4.116-1.458 5.364-.912 1.236-2.328 1.854-4.248 1.854a5.839 5.839 0 0 1-2.826-.702 3.703 3.703 0 0 1-1.764-2.07l1.044.054c-.492.924-1.164 1.572-2.016 1.944a6.464 6.464 0 0 1-2.61.558c-1.212 0-2.28-.258-3.204-.774a5.682 5.682 0 0 1-2.178-2.214c-.528-.948-.792-2.046-.792-3.294 0-1.284.276-2.394.828-3.33a5.77 5.77 0 0 1 2.232-2.196c.936-.516 1.992-.774 3.168-.774.78 0 1.59.162 2.43.486.852.324 1.512.78 1.98 1.368l-.738.936V8.664h2.412l-.054 6.462c0 .924.18 1.62.54 2.088.36.468.894.702 1.602.702.624 0 1.104-.174 1.44-.522.348-.36.588-.846.72-1.458a10.66 10.66 0 0 0 .252-2.106c.036-1.86-.24-3.426-.828-4.698-.588-1.272-1.386-2.298-2.394-3.078a9.499 9.499 0 0 0-3.294-1.71c-1.2-.36-2.394-.54-3.582-.54-1.68 0-3.174.27-4.482.81-1.308.528-2.412 1.278-3.312 2.25-.888.96-1.56 2.1-2.016 3.42-.444 1.308-.654 2.748-.63 4.32.048 1.56.33 2.964.846 4.212a9.324 9.324 0 0 0 2.16 3.204 9.38 9.38 0 0 0 3.276 2.034c1.26.468 2.64.702 4.14.702.84 0 1.674-.096 2.502-.288.84-.18 1.608-.438 2.304-.774l1.026 2.808c-.924.432-1.896.75-2.916.954a14.649 14.649 0 0 1-3.078.324Zm-.144-10.098c.852 0 1.566-.246 2.142-.738.576-.492.864-1.326.864-2.502 0-1.068-.258-1.872-.774-2.412-.504-.552-1.218-.828-2.142-.828-1.092 0-1.908.288-2.448.864-.54.576-.81 1.368-.81 2.376 0 1.032.276 1.83.828 2.394.564.564 1.344.846 2.34.846Z"></path><path class="fill-blue-600 dark:fill-blue-500" d="M51.799 7.813V5.545h13.509v2.268H59.86V23h-2.624V7.812h-5.438ZM39.392 23h-2.795l6.28-17.455h3.043L52.203 23h-2.796L44.472 8.716h-.137L39.392 23Zm.469-6.835h9.068v2.216h-9.068v-2.216Z"></path><path class="fill-zinc-700 dark:fill-zinc-400" d="M161.144 5.545V23h-2.548V5.545h2.548ZM149.649 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.506 1.017.758 2.204.758 3.562 0 1.352-.252 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.384-1.468.384-2.31 0-.834-.128-1.602-.384-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.801 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.194.631 1.995.631ZM136.032 23.264c-1.267 0-2.358-.287-3.273-.86-.909-.58-1.608-1.378-2.096-2.395-.489-1.018-.733-2.182-.733-3.495 0-1.33.25-2.503.75-3.52.5-1.022 1.204-1.82 2.113-2.395.909-.573 1.98-.86 3.213-.86.995 0 1.881.184 2.659.554a4.764 4.764 0 0 1 1.884 1.534c.483.659.77 1.429.861 2.31h-2.48a2.974 2.974 0 0 0-.938-1.586c-.483-.443-1.13-.665-1.943-.665-.71 0-1.332.188-1.866.563-.529.37-.941.898-1.236 1.585-.296.682-.443 1.489-.443 2.42 0 .955.144 1.779.434 2.472.29.693.699 1.23 1.227 1.61.535.382 1.162.572 1.884.572.483 0 .92-.088 1.313-.264.397-.182.73-.44.997-.776.272-.335.463-.739.571-1.21h2.48a4.823 4.823 0 0 1-.827 2.267 4.76 4.76 0 0 1-1.849 1.568c-.767.38-1.668.571-2.702.571ZM121.571 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.131-2.361-.505-1.012-.758-2.194-.758-3.546 0-1.358.253-2.545.758-3.562.506-1.017 1.216-1.807 2.131-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.009-2.139c.795 0 1.454-.21 1.977-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.977-.639-.802 0-1.466.213-1.995.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.645 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM113.379 9.91v2.044h-7.151V9.91h7.151Zm-5.233-3.137h2.548v12.383c0 .495.074.867.221 1.117.148.244.339.412.572.503.238.085.497.127.775.127.205 0 .384-.014.537-.042l.358-.068.46 2.105a4.307 4.307 0 0 1-.63.17 4.992 4.992 0 0 1-1.023.102 4.483 4.483 0 0 1-1.875-.358 3.208 3.208 0 0 1-1.406-1.159c-.358-.522-.537-1.179-.537-1.968V6.773ZM98.321 23.264c-1.227 0-2.298-.281-3.213-.843-.915-.563-1.625-1.35-2.13-2.361-.506-1.012-.76-2.194-.76-3.546 0-1.358.254-2.545.76-3.562.505-1.017 1.215-1.807 2.13-2.37.915-.562 1.986-.843 3.213-.843s2.298.28 3.213.843 1.625 1.353 2.131 2.37c.505 1.017.758 2.204.758 3.562 0 1.352-.253 2.534-.758 3.546-.506 1.011-1.216 1.798-2.131 2.36-.915.563-1.986.844-3.213.844Zm.008-2.139c.796 0 1.455-.21 1.978-.63.523-.421.909-.98 1.159-1.68.256-.699.383-1.468.383-2.31 0-.834-.127-1.602-.383-2.3-.25-.705-.636-1.27-1.159-1.697-.523-.426-1.182-.639-1.978-.639-.8 0-1.465.213-1.994.64-.522.426-.912.991-1.167 1.695a6.789 6.789 0 0 0-.375 2.302c0 .84.125 1.61.375 2.31.255.698.644 1.258 1.167 1.678.529.42 1.193.631 1.995.631ZM84.065 23V9.91h2.463v2.079h.136a3.164 3.164 0 0 1 1.261-1.662 3.61 3.61 0 0 1 2.063-.614 10.896 10.896 0 0 1 1.082.06v2.437a4.577 4.577 0 0 0-.545-.094 5.202 5.202 0 0 0-.784-.06c-.603 0-1.14.129-1.611.384a2.85 2.85 0 0 0-1.517 2.566V23h-2.548ZM68.918 23V5.545h6.221c1.358 0 2.483.248 3.375.742.892.494 1.56 1.17 2.003 2.028.443.853.665 1.813.665 2.881 0 1.074-.225 2.04-.674 2.898-.443.852-1.113 1.528-2.01 2.028-.893.494-2.015.742-3.367.742h-4.279V14.63h4.04c.858 0 1.554-.148 2.088-.444.534-.3.926-.71 1.176-1.227.25-.517.375-1.105.375-1.764 0-.66-.125-1.244-.375-1.756-.25-.511-.645-.912-1.184-1.201-.534-.29-1.239-.435-2.114-.435h-3.307V23h-2.633Z"></path></svg></a></div><div class="flex items-center gap-5"><nav class="hidden md:block"><ul role="list" class="flex items-center gap-8"><li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/sdks">SDKs</a></li><li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="https://docs.bsky.app/blog">Blog</a></li><li><a class="text-sm leading-5 text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="https://github.com/bluesky-social/atproto">GitHub</a></li><select class="block w-full appearance-none rounded-md border-0 py-1.5 pl-3 pr-3 text-gray-900 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-gray-800 dark:text-gray-100 dark:ring-gray-700"><option value="en" selected="">English</option><option value="pt">Português</option><option value="ja">日本語</option></select></ul></nav><div class="hidden md:block md:h-5 md:w-px md:bg-zinc-900/10 md:dark:bg-white/15"></div><div class="flex gap-4"><div class="contents lg:hidden"><button type="button" class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 ui-not-focus-visible:outline-none lg:hidden dark:hover:bg-white/5" aria-label="Find something..."><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5 stroke-zinc-900 dark:stroke-white"><path stroke-linecap="round" stroke-linejoin="round" d="M12.01 12a4.25 4.25 0 1 0-6.02-6 4.25 4.25 0 0 0 6.02 6Zm0 0 3.24 3.25"></path></svg></button><!--$--><span hidden="" style="position:fixed;top:1px;left:1px;width:1px;height:0;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;display:none"></span><!--/$--></div><button type="button" class="flex h-6 w-6 items-center justify-center rounded-md transition hover:bg-zinc-900/5 dark:hover:bg-white/5" aria-label="Toggle theme"><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="h-5 w-5 stroke-zinc-900 dark:hidden"><path d="M12.5 10a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Z"></path><path stroke-linecap="round" d="M10 5.5v-1M13.182 6.818l.707-.707M14.5 10h1M13.182 13.182l.707.707M10 15.5v-1M6.11 13.889l.708-.707M4.5 10h1M6.11 6.111l.708.707"></path></svg><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="hidden h-5 w-5 stroke-white dark:block"><path d="M15.224 11.724a5.5 5.5 0 0 1-6.949-6.949 5.5 5.5 0 1 0 6.949 6.949Z"></path></svg></button></div></div></div><nav class="hidden lg:mt-10 lg:block"><ul role="list"><li class="md:hidden"><a class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/">API</a></li><li class="md:hidden"><a class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="#">Documentation</a></li><li class="md:hidden"><a class="block py-1 text-sm text-zinc-600 transition hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="#">Support</a></li><li class="relative mt-6 md:mt-0"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Home</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/"><span class="truncate">Introduction</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/sdks"><span class="truncate">SDKs</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/glossary"><span class="truncate">Glossary</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/faq"><span class="truncate">FAQ</span></a></li></ul></div></li><li class="relative mt-6"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Building apps</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/applications"><span class="truncate">Quick start</span></a></li></ul></div></li><li class="relative mt-6"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Guides</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/overview"><span class="truncate">Overview</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/self-hosting"><span class="truncate">Self-hosting</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/identity"><span class="truncate">Identity</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/data-repos"><span class="truncate">Data repos</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/guides/lexicon"><span class="truncate">Schemas & Lexicon</span></a></li></ul></div></li><li class="relative mt-6"><h2 class="text-xs font-semibold text-zinc-900 dark:text-white">Specs</h2><div class="relative mt-3 pl-2"><div class="absolute inset-y-0 left-2 w-px bg-zinc-900/10 dark:bg-white/5"></div><ul role="list" class="border-l border-transparent"><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/atp"><span class="truncate">AT Protocol</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/data-model"><span class="truncate">Data Model</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/lexicon"><span class="truncate">Lexicon</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/cryptography"><span class="truncate">Cryptography</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/repository"><span class="truncate">Repository</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/blob"><span class="truncate">Blobs</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/label"><span class="truncate">Labels</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/xrpc"><span class="truncate">HTTP API (XRPC)</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/oauth"><span class="truncate">OAuth</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/event-stream"><span class="truncate">Event Stream</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/did"><span class="truncate">DID</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/handle"><span class="truncate">Handle</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/nsid"><span class="truncate">NSID</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/record-key"><span class="truncate">Record Key</span></a></li><li class="relative"><a class="flex justify-between gap-2 py-1 pr-3 text-sm transition pl-4 text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white" href="/specs/at-uri-scheme"><span class="truncate">URI Scheme</span></a></li></ul></div></li></ul></nav></div></header><div class="relative flex h-full flex-col px-4 pt-14 sm:px-6 lg:px-8"><main class="flex-auto"><article class="flex h-full flex-col pb-24 pt-16"><div class="flex-auto prose dark:prose-invert [html_:where(&>*)]:mx-auto [html_:where(&>*)]:max-w-2xl [html_:where(&>*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(&>*)]:lg:max-w-3xl"><h1>ATProto for distributed systems engineers</h1> <p><em>Sep 3, 2024</em></p> <p class="lead">AT Protocol is the tech developed at <a href="https://bsky.app">Bluesky</a> for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering.</p> <p class="lead">If you've ever built a backend with <a href="https://milinda.pathirage.org/kappa-architecture.com/">stream-processing</a>, then you're familiar with the kind of systems we'll be exploring. If you're not — no worries! We'll step through it.</p> <h2 class="scroll-mt-24" id="scaling-the-traditional-web-backend"><a class="group text-inherit no-underline hover:text-inherit" href="#scaling-the-traditional-web-backend">Scaling the traditional Web backend</a></h2> <p>The classic, happy Web architecture is the “one big SQL database” behind our app server. The app talks to the database and handles requests from the frontend.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1169" height="1600" decoding="async" data-nimg="1" class="w-full dark:invert max-w-md mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage1.c6dcf0c4.png&w=1200&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage1.c6dcf0c4.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage1.c6dcf0c4.png&w=3840&q=75"/></div> <p>As our application grows, we hit some performance limits so we toss some caches into the stack.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1229" decoding="async" data-nimg="1" class="w-full dark:invert max-w-2xl mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage2.693478de.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage2.693478de.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage2.693478de.png&w=3840&q=75"/></div> <p>Then let's say we scale our database horizontally through sharding and replicas.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1457" decoding="async" data-nimg="1" class="w-full dark:invert max-w-xl mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage3.88182e12.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage3.88182e12.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage3.88182e12.png&w=3840&q=75"/></div> <p>This is pretty good, but we're building a social network with hundreds of millions of users; even this model hits limits. The problem is that our SQL database is “<a href="https://en.wikipedia.org/wiki/Strong_consistency">strongly consistent</a>” which means the state is kept uniformly in sync across the system. Maintaining strong consistency incurs a performance cost which becomes our bottleneck.</p> <p>If we can relax our system to use “<a href="https://en.wikipedia.org/wiki/Eventual_consistency">eventual consistency</a>,” we can scale much further. We start by switching to a NoSQL cluster.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1492" decoding="async" data-nimg="1" class="w-full dark:invert max-w-lg mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage4.524d9b4a.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage4.524d9b4a.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage4.524d9b4a.png&w=3840&q=75"/></div> <p>This is better for scaling, but without SQL it's becoming harder to build our queries. It turns out that SQL databases have a lot of useful features, like JOIN and aggregation queries. In fact, our NoSQL database is really just a key-value store. Writing features is becoming a pain!</p> <p>To fix this, we need to write programs which generate precomputed views of our dataset. These views are essentially like cached queries. We even duplicate the canonical data into these views so they're very fast.</p> <p>We'll call these our View servers.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1378" height="1038" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage5.5e756a1a.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage5.5e756a1a.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage5.5e756a1a.png&w=3840&q=75"/></div> <p>Now we notice that keeping our view servers synced with the canonical data in the NoSQL cluster is tricky. Sometimes our view servers crash and miss updates. We need to make sure that our views stay reliably up-to-date.</p> <p>To solve this, we introduce an event log (such as <a href="https://kafka.apache.org/">Kafka</a>). That log records and broadcasts all the changes to the NoSQL cluster. Our view servers listen to — and replay — that log to ensure they never miss an update, even when they need to restart.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1183" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage6.9e038bff.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage6.9e038bff.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage6.9e038bff.png&w=3840&q=75"/></div> <p>We've now arrived at a <a href="https://milinda.pathirage.org/kappa-architecture.com/">stream processing architecture</a>, and while there are a lot more details we could cover, this is enough for now.</p> <p>The good news is that this architecture scales pretty well. We've given up strong consistency and sometimes our read queries lag behind the most up to date version of the data, but the service doesn't drop writes or enter an incorrect state.</p> <p>In a way, what we've done is custom-built a database by <a href="https://www.youtube.com/watch?v=fU9hR3kiOK0">turning it inside-out</a>. We simplified the canonical storage into a NoSQL cluster, and then built our own querying engine with the view servers. It's a lot less convenient to build with, but it scales.</p> <h2 class="scroll-mt-24" id="decentralizing-our-high-scale-backend"><a class="group text-inherit no-underline hover:text-inherit" href="#decentralizing-our-high-scale-backend">Decentralizing our high-scale backend</a></h2> <p>The goal of AT Protocol is to interconnect applications so that their backends share state, including user accounts and content.</p> <p>How can we do that? If we look at our diagram, we can see that most of the system is isolated from the outside world, with only the App server providing a public interface.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1094" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage7.0e152275.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage7.0e152275.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage7.0e152275.png&w=3840&q=75"/></div> <p>Our goal is to break this isolation down so that other people can join our NoSQL cluster, our event log, our view servers, and so on.</p> <p>Here's how it's going to look:</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1109" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage8.8cde2a8c.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage8.8cde2a8c.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage8.8cde2a8c.png&w=3840&q=75"/></div> <p>Each of these internal services are now external services. They have public APIs which anybody can consume. On top of that, anybody can create their own instances of these services.</p> <p>Our goal is to make it so anybody can contribute to this decentralized backend. That means that we don't just want one NoSQL cluster, or one View server. We want lots of these servers working together. So really it's more like this:</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="925" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage9.a51ae040.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage9.a51ae040.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage9.a51ae040.png&w=3840&q=75"/></div> <p>So how do we make all of these services work together?</p> <h2 class="scroll-mt-24" id="unifying-the-data-model"><a class="group text-inherit no-underline hover:text-inherit" href="#unifying-the-data-model">Unifying the data model</a></h2> <p>We're going to establish a shared data model called the <a href="/guides/data-repos">“user data repository.”</a></p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="438" height="464" decoding="async" data-nimg="1" class="w-full dark:invert max-w-72 mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage10.6817a213.png&w=640&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage10.6817a213.png&w=1080&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage10.6817a213.png&w=1080&q=75"/></div> <p>Every data repository contains JSON documents, which we'll call “records”.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="838" height="554" decoding="async" data-nimg="1" class="w-full dark:invert max-w-lg mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage11.4f73b780.png&w=1080&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage11.4f73b780.png&w=1920&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage11.4f73b780.png&w=1920&q=75"/></div> <p>For organizational purposes, we'll bucket these records into “collections.”</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="870" height="570" decoding="async" data-nimg="1" class="w-full dark:invert max-w-lg mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage12.bd8affbd.png&w=1080&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage12.bd8affbd.png&w=1920&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage12.bd8affbd.png&w=1920&q=75"/></div> <p>Now we're going to opinionate our NoSQL services so they all use this <a href="/guides/data-repos">data repository</a> model.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1010" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage13.88b82924.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage13.88b82924.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage13.88b82924.png&w=3840&q=75"/></div> <p>Remember: the data repo services are still basically NoSQL stores, it's just that they're now organized in a very specific way:</p> <ol> <li>Each user has a data repository.</li> <li>Each repository has collections.</li> <li>Each collection is an ordered K/V store of JSON documents.</li> </ol> <p>Since the data repositories can be hosted by anybody, we need to give them <a href="/specs/at-uri-scheme">URLs</a>.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="380" height="354" decoding="async" data-nimg="1" class="w-full dark:invert max-w-72 mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage14.47dc55b6.png&w=384&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage14.47dc55b6.png&w=828&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage14.47dc55b6.png&w=828&q=75"/></div> <p>While we're at it, let's create a <a href="/specs/at-uri-scheme">whole URL scheme</a> for our records too.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="816" height="600" decoding="async" data-nimg="1" class="w-full dark:invert max-w-lg mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage15.b82b14bb.png&w=828&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage15.b82b14bb.png&w=1920&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage15.b82b14bb.png&w=1920&q=75"/></div> <p>Great! Also, since we're going to be syncing these records around the Internet, it would be a good idea to cryptographically sign them so that we know they're authentic.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1510" height="964" decoding="async" data-nimg="1" class="w-full dark:invert max-w-lg mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage16.1d02511f.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage16.1d02511f.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage16.1d02511f.png&w=3840&q=75"/></div> <h2 class="scroll-mt-24" id="charting-the-flow-of-data"><a class="group text-inherit no-underline hover:text-inherit" href="#charting-the-flow-of-data">Charting the flow of data</a></h2> <p>Now that we've set up our high-scale decentralized backend, let's map out how an application actually works on ATProto.</p> <p>Since we're making a new app, we're going to want two things: an app server (which hosts our API & frontend) and a view server (which collects data from the network for us). We often bundle the app & view servers, and so we can just call it an “Appview.” Let's start there:</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1294" height="876" decoding="async" data-nimg="1" class="w-full dark:invert max-w-md mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage17.bdea24e3.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage17.bdea24e3.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage17.bdea24e3.png&w=3840&q=75"/></div> <p>A user logs into our app using OAuth. In the process, they tell us which server hosts their data repository, <em>and</em> they give us permission to read and write to it.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1256" height="1338" decoding="async" data-nimg="1" class="w-full dark:invert max-w-md mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage18.5b6c97f8.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage18.5b6c97f8.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage18.5b6c97f8.png&w=3840&q=75"/></div> <p>We're off to a good start — we can read and write JSON documents in the user's repo. If they already have data from other apps (like a profile) we can read that data too. If we were building a singleplayer app, we'd already be done.</p> <p>But let's chart what happens when we write a JSON document.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1324" decoding="async" data-nimg="1" class="w-full dark:invert max-w-xl mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage19.6cb5a9de.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage19.6cb5a9de.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage19.6cb5a9de.png&w=3840&q=75"/></div> <p>This commits the document to the repo, then fires off a write into the event logs which are listening to the repo.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="827" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage20.032fb864.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage20.032fb864.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage20.032fb864.png&w=3840&q=75"/></div> <p>From there, the event gets sent to any view services that are listening — including our own!</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="846" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage21.d3fed181.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage21.d3fed181.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage21.d3fed181.png&w=3840&q=75"/></div> <p>Why are we listening to the event stream if we're the one making the write? Because we're not the only ones making writes! There are lots of user repos generating events, and lots of apps writing to them!</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="950" height="1250" decoding="async" data-nimg="1" class="w-full dark:invert max-w-lg mx-auto" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage22.da5edbc6.png&w=1080&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage22.da5edbc6.png&w=1920&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage22.da5edbc6.png&w=1920&q=75"/></div> <p>So we can see a kind of circular data flow throughout our decentralized backend, with writes being committed to the data repos, then emitted through the event logs into the view servers, where they can be read by our applications.</p> <div class="max-w-2xl lg:max-w-3xl"><img alt="" loading="lazy" width="1600" height="1013" decoding="async" data-nimg="1" class="w-full dark:invert max-w-full" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage23.5342b3db.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage23.5342b3db.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage23.5342b3db.png&w=3840&q=75"/></div> <p>And (one hopes) that this network continues to scale: not just to add capacity, but to create a wider variety of applications sharing in this open applications network.</p> <div class="max-w-2xl lg:max-w-3xl"><img loading="lazy" width="1504" height="1082" decoding="async" data-nimg="1" class="w-full dark:invert max-w-2xl" style="color:transparent" srcSet="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage24.2111e734.png&w=1920&q=75 1x, /_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage24.2111e734.png&w=3840&q=75 2x" src="/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fimage24.2111e734.png&w=3840&q=75"/></div> <h2 class="scroll-mt-24" id="building-practical-open-systems"><a class="group text-inherit no-underline hover:text-inherit" href="#building-practical-open-systems">Building practical open systems</a></h2> <p>The AT Protocol merges p2p tech with high-scale systems practices. Our founding engineers were core <a href="https://en.wikipedia.org/wiki/InterPlanetary_File_System">IPFS</a> and <a href="https://en.wikipedia.org/wiki/Dat_(software)">Dat</a> engineers, and Martin Kleppmann — the author of <a href="https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/">Data Intensive Applications</a> — is an active technical advisor.</p> <p>Before Bluesky was started, we established a clear requirement of “no steps backwards.” We wanted the network to feel as convenient and global as every social app before it, while still working as an open network. This is why, when we looked at federation and blockchains, the scaling limits of those architectures stood out to us. Our solution was to take standard practices for high scale backends, and then apply the techniques we used in peer-to-peer systems to create an open network.</p> <div class="not-prose group relative mt-12 flex rounded-2xl bg-zinc-50 transition-shadow hover:shadow-md hover:shadow-zinc-900/5 dark:bg-white/2.5 dark:hover:shadow-black/5"><div class="pointer-events-none"><div class="absolute inset-0 rounded-2xl transition duration-300 [mask-image:linear-gradient(white,transparent)] group-hover:opacity-50"><svg aria-hidden="true" class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/[0.02] stroke-black/5 dark:fill-white/1 dark:stroke-white/2.5"><defs><pattern id=":Rb5cvfeqkva:" width="72" height="56" patternUnits="userSpaceOnUse" x="50%" y="22"><path d="M.5 56V.5H72" fill="none"></path></pattern></defs><rect width="100%" height="100%" stroke-width="0" fill="url(#:Rb5cvfeqkva:)"></rect><svg x="50%" y="22" class="overflow-visible"><rect stroke-width="0" width="73" height="57" x="0" y="56"></rect></svg></svg></div><div class="absolute inset-0 rounded-2xl bg-gradient-to-r from-[#e9f7ff] to-[#cee7f9] opacity-0 transition duration-300 group-hover:opacity-100 dark:from-[#12294d] dark:to-[#003964]" style="mask-image:radial-gradient(180px at 0px 0px, white, transparent);-webkit-mask-image:radial-gradient(180px at 0px 0px, white, transparent)"></div><div class="absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100" style="mask-image:radial-gradient(180px at 0px 0px, white, transparent);-webkit-mask-image:radial-gradient(180px at 0px 0px, white, transparent)"><svg aria-hidden="true" class="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 dark:fill-white/2.5 dark:stroke-white/10"><defs><pattern id=":R2r5cvfeqkva:" width="72" height="56" patternUnits="userSpaceOnUse" x="50%" y="22"><path d="M.5 56V.5H72" fill="none"></path></pattern></defs><rect width="100%" height="100%" stroke-width="0" fill="url(#:R2r5cvfeqkva:)"></rect><svg x="50%" y="22" class="overflow-visible"><rect stroke-width="0" width="73" height="57" x="0" y="56"></rect></svg></svg></div></div><div class="absolute inset-0 rounded-2xl ring-1 ring-inset ring-zinc-900/7.5 group-hover:ring-zinc-900/10 dark:ring-white/10 dark:group-hover:ring-white/20"></div><div class="relative rounded-2xl px-4 pb-4 pt-16"><h3 class="mt-4 text-sm font-semibold leading-7 text-zinc-900 dark:text-white"><a href="/"><span class="absolute inset-0 rounded-2xl"></span>Ready to learn more?</a></h3><div class="mt-1 flex items-center gap-1 text-sm text-zinc-600 dark:text-zinc-400"><span>Specs, guides, and SDKs can be found here.</span><svg viewBox="0 0 20 20" fill="none" aria-hidden="true" class="mt-0.5 h-5 w-5"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9"></path></svg></div></div></div></div></article></main><footer class="mx-auto w-full max-w-2xl space-y-10 pb-16 lg:max-w-5xl"><div class="flex flex-col items-center justify-between gap-5 border-t border-zinc-900/5 pt-8 sm:flex-row dark:border-white/5"><p class="text-xs text-zinc-600 dark:text-zinc-400">© Copyright <!-- -->2024<!-- -->. All rights reserved.</p><div class="flex gap-4"><a class="group" href="https://bsky.app/profile/atproto.com"><span class="sr-only">Follow us on Bluesky</span><svg viewBox="0 0 360 320" aria-hidden="true" class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 dark:group-hover:fill-zinc-500"><path d="M180 141.964C163.699 110.262 119.308 51.1817 78.0347 22.044C38.4971 -5.86834 23.414 -1.03207 13.526 3.43594C2.08093 8.60755 0 26.1785 0 36.5164C0 46.8542 5.66748 121.272 9.36416 133.694C21.5786 174.738 65.0603 188.607 105.104 184.156C107.151 183.852 109.227 183.572 111.329 183.312C109.267 183.642 107.19 183.924 105.104 184.156C46.4204 192.847 -5.69621 214.233 62.6582 290.33C137.848 368.18 165.705 273.637 180 225.702C194.295 273.637 210.76 364.771 295.995 290.33C360 225.702 313.58 192.85 254.896 184.158C252.81 183.926 250.733 183.645 248.671 183.315C250.773 183.574 252.849 183.855 254.896 184.158C294.94 188.61 338.421 174.74 350.636 133.697C354.333 121.275 360 46.8568 360 36.519C360 26.1811 357.919 8.61012 346.474 3.43851C336.586 -1.02949 321.503 -5.86576 281.965 22.0466C240.692 51.1843 196.301 110.262 180 141.964Z"></path></svg></a><a class="group" href="https://github.com/bluesky-social"><span class="sr-only">Follow us on GitHub</span><svg viewBox="0 0 20 20" aria-hidden="true" class="h-5 w-5 fill-zinc-700 transition group-hover:fill-zinc-900 dark:group-hover:fill-zinc-500"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 1.667c-4.605 0-8.334 3.823-8.334 8.544 0 3.78 2.385 6.974 5.698 8.106.417.075.573-.182.573-.406 0-.203-.011-.875-.011-1.592-2.093.397-2.635-.522-2.802-1.002-.094-.246-.5-1.005-.854-1.207-.291-.16-.708-.556-.01-.567.656-.01 1.124.62 1.281.876.75 1.292 1.948.93 2.427.705.073-.555.291-.93.531-1.143-1.854-.213-3.791-.95-3.791-4.218 0-.929.322-1.698.854-2.296-.083-.214-.375-1.09.083-2.265 0 0 .698-.224 2.292.876a7.576 7.576 0 0 1 2.083-.288c.709 0 1.417.096 2.084.288 1.593-1.11 2.291-.875 2.291-.875.459 1.174.167 2.05.084 2.263.53.599.854 1.357.854 2.297 0 3.278-1.948 4.005-3.802 4.219.302.266.563.78.563 1.58 0 1.143-.011 2.061-.011 2.35 0 .224.156.491.573.405a8.365 8.365 0 0 0 4.11-3.116 8.707 8.707 0 0 0 1.567-4.99c0-4.721-3.73-8.545-8.334-8.545Z"></path></svg></a></div></div></footer></div></div></div><script src="/_next/static/chunks/webpack-496a37b6c26ccd6a.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/786ca9324488b5df.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n5:I[4707,[],\"\"]\n7:I[6423,[],\"\"]\na:I[1060,[],\"\"]\n6:[\"locale\",\"en\",\"d\"]\nb:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"UWK7gfTTdjb8rbg3vyV6o\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"articles\",\"atproto-for-distsys-engineers\"],\"initialTree\":[\"\",{\"children\":[[\"locale\",\"en\",\"d\"],{\"children\":[\"articles\",{\"children\":[\"atproto-for-distsys-engineers\",{\"children\":[\"__PAGE__\",{}]}]}]},\"$undefined\",\"$undefined\",true]}],\"initialSeedData\":[\"\",{\"children\":[[\"locale\",\"en\",\"d\"],{\"children\":[\"articles\",{\"children\":[\"atproto-for-distsys-engineers\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",\"$L4\",null],null],null]},[null,[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"$6\",\"children\",\"articles\",\"children\",\"atproto-for-distsys-engineers\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[null,[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"$6\",\"children\",\"articles\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/786ca9324488b5df.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],\"$L8\"],null],null]},[null,[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]],null],\"couldBeIntercepted\":false,\"initialHead\":[null,\"$L9\"],\"globalErrorComponent\":\"$a\",\"missingSlots\":\"$Wb\"}]\n"])</script><script>self.__next_f.push([1,"c:I[2972,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"474\",\"static/chunks/app/%5Blocale%5D/articles/atproto-for-distsys-engineers/page-3c97c5d3e0a96ea7.js\"],\"\"]\nd:I[1684,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"474\",\"static/chunks/app/%5Blocale%5D/articles/atproto-for-distsys-engineers/page-3c97c5d3e0a96ea7.js\"],\"Heading\"]\ne:I[5878,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"474\",\"static/chunks/app/%5Blocale%5D/articles/atproto-for-distsys-engineers/page-3c97c5d3e0a96ea7.js\"],\"Image\"]\nf:I[5531,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"474\",\"static/chunks/app/%5Blocale%5D/articles/atproto-for-distsys-engineers/page-3c97c5d3e0a96ea7.js\"],\"FooterCTA\"]\n"])</script><script>self.__next_f.push([1,"4:[\"$\",\"article\",null,{\"className\":\"flex h-full flex-col pb-24 pt-16\",\"children\":[\"$\",\"div\",null,{\"className\":\"flex-auto prose dark:prose-invert [html_:where(\u0026\u003e*)]:mx-auto [html_:where(\u0026\u003e*)]:max-w-2xl [html_:where(\u0026\u003e*)]:lg:mx-[calc(50%-min(50%,theme(maxWidth.lg)))] [html_:where(\u0026\u003e*)]:lg:max-w-3xl\",\"children\":[[\"$\",\"h1\",null,{\"children\":\"ATProto for distributed systems engineers\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"$\",\"em\",null,{\"children\":\"Sep 3, 2024\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"AT Protocol is the tech developed at \",[\"$\",\"$Lc\",null,{\"href\":\"https://bsky.app\",\"children\":\"Bluesky\"}],\" for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering.\"],\"className\":\"lead\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"If you've ever built a backend with \",[\"$\",\"$Lc\",null,{\"href\":\"https://milinda.pathirage.org/kappa-architecture.com/\",\"children\":\"stream-processing\"}],\", then you're familiar with the kind of systems we'll be exploring. If you're not — no worries! We'll step through it.\"],\"className\":\"lead\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"level\":2,\"id\":\"scaling-the-traditional-web-backend\",\"children\":\"Scaling the traditional Web backend\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"The classic, happy Web architecture is the “one big SQL database” behind our app server. The app talks to the database and handles requests from the frontend.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image1.c6dcf0c4.png\",\"height\":1600,\"width\":1169,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAICAMAAADtGH4KAAAAOVBMVEX////9/f79/f38/P38/Pz7/P37+/z7+/v6+/36+/z6+vr4+vz5+fn4+Pn3+Pv4+Pj39/f29vbz8/OjlAnMAAAAMUlEQVR42hXBhxEAIAgAsVewYWf/YT0T4h45QFRdCbABUNy3BPTa7AJW+K43AY5X4QEhmQE9eHSfzwAAAABJRU5ErkJggg==\",\"blurWidth\":6,\"blurHeight\":8},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-md mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"As our application grows, we hit some performance limits so we toss some caches into the stack.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image2.693478de.png\",\"height\":1229,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAQlBMVEX///////7+/v79/v79/f79/f38/P38/Pz7/P77/P37+/v6+vr5+vz6+vn5+vr5+fr5+fn4+Pf29vb09fb09PTy8vPxux16AAAANElEQVR42g3JyQGAIBAEwXZcUFBuyD9VqG8BSiUgQv6SvxHm4mpOYL72+hunYn9AAHO8lzYklgFejGhgHQAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":6},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-2xl mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Then let's say we scale our database horizontally through sharding and replicas.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image3.88182e12.png\",\"height\":1457,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAMAAAACh/xsAAAASFBMVEX////+/v79/f79/f38/P38/Pz7/P37+/37+/z7+/v6+/35+vz5+fv5+fn4+fv3+Pv4+Pj39/f29vf29vb19fX09PTz8/Px8fEmJqe+AAAAOklEQVR42hXGWxKAIAzF0FwLaH0jovvfKdNMPg6639b2bUWW3ZaSQLVnIvvqcxUQ7qD46GlCgf+c0QA2rQGKsZUqBgAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":7},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-xl mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"This is pretty good, but we're building a social network with hundreds of millions of users; even this model hits limits. The problem is that our SQL database is “\",[\"$\",\"$Lc\",null,{\"href\":\"https://en.wikipedia.org/wiki/Strong_consistency\",\"children\":\"strongly consistent\"}],\"” which means the state is kept uniformly in sync across the system. Maintaining strong consistency incurs a performance cost which becomes our bottleneck.\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"If we can relax our system to use “\",[\"$\",\"$Lc\",null,{\"href\":\"https://en.wikipedia.org/wiki/Eventual_consistency\",\"children\":\"eventual consistency\"}],\",” we can scale much further. We start by switching to a NoSQL cluster.\"]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image4.524d9b4a.png\",\"height\":1492,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAMAAAACh/xsAAAAXVBMVEX////+/v79/v79/f79/f38/f78/Pz7+/z7+/v6+/v5+v36+vr5+vz5+fr4+fz5+fn4+fv4+Pr3+Pv4+Pj39/f29vb19fX09PTz8/Px8vPw8PDo6Ojm5ubk5OTj4+K9S/5pAAAAPElEQVR42hXGRxaAIBBEwd9iRh2zmLj/MXnUqtD7f/GeZtTaaU3hQEfwTkB5bfs6CFEbkDOGrlKuf5ZeJFELAiAjJoKVAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":7},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-lg mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"This is better for scaling, but without SQL it's becoming harder to build our queries. It turns out that SQL databases have a lot of useful features, like JOIN and aggregation queries. In fact, our NoSQL database is really just a key-value store. Writing features is becoming a pain!\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"To fix this, we need to write programs which generate precomputed views of our dataset. These views are essentially like cached queries. We even duplicate the canonical data into these views so they're very fast.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"We'll call these our View servers.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image5.5e756a1a.png\",\"height\":1038,\"width\":1378,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAS1BMVEX////+/v/+/v79/v79/f78/f78/P38/Pz7+/36+/36+vr5+vz5+fn4+fv4+Pn3+Pr39/f19fX09PTy8vLy8fHw8fLq6urp6enn5+e/ACOpAAAANElEQVR42gWACRJAMBAE2wjLiCPr/P9LFdC+99kX6nk4rYLvmrnNop9kxyiAaKUDibjWgR83tAGySEEhtAAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":6},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Now we notice that keeping our view servers synced with the canonical data in the NoSQL cluster is tricky. Sometimes our view servers crash and miss updates. We need to make sure that our views stay reliably up-to-date.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"To solve this, we introduce an event log (such as \",[\"$\",\"$Lc\",null,{\"href\":\"https://kafka.apache.org/\",\"children\":\"Kafka\"}],\"). That log records and broadcasts all the changes to the NoSQL cluster. Our view servers listen to — and replay — that log to ensure they never miss an update, even when they need to restart.\"]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image6.9e038bff.png\",\"height\":1183,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAMAAADJ2y/JAAAAV1BMVEX///////7+/v/+/v79/v79/f79/f38/Pz7/P37/Pz7+/37+/v6+vr5+vr5+fr5+fn4+Pj3+Pn39/f39/b29vb19fX09PTy8/Ty8vPx8fHw7+/u7u7t7vArlseSAAAAN0lEQVR42gWACRaAEBBAv6ahRSmhwv3P6VF+F3Nala+73Oq5EIq1KSgcr+yX34RphugxgPDcagZHxwIhasJG6wAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":6},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"We've now arrived at a \",[\"$\",\"$Lc\",null,{\"href\":\"https://milinda.pathirage.org/kappa-architecture.com/\",\"children\":\"stream processing architecture\"}],\", and while there are a lot more details we could cover, this is enough for now.\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"The good news is that this architecture scales pretty well. We've given up strong consistency and sometimes our read queries lag behind the most up to date version of the data, but the service doesn't drop writes or enter an incorrect state.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"In a way, what we've done is custom-built a database by \",[\"$\",\"$Lc\",null,{\"href\":\"https://www.youtube.com/watch?v=fU9hR3kiOK0\",\"children\":\"turning it inside-out\"}],\". We simplified the canonical storage into a NoSQL cluster, and then built our own querying engine with the view servers. It's a lot less convenient to build with, but it scales.\"]}],\"\\n\",[\"$\",\"$Ld\",null,{\"level\":2,\"id\":\"decentralizing-our-high-scale-backend\",\"children\":\"Decentralizing our high-scale backend\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"The goal of AT Protocol is to interconnect applications so that their backends share state, including user accounts and content.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"How can we do that? If we look at our diagram, we can see that most of the system is isolated from the outside world, with only the App server providing a public interface.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image7.0e152275.png\",\"height\":1094,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAWElEQVR42iWKCQ6DQAwD9/9/Le2me+RwAgYixRp53FT1cxzmttYSkTGGBwJo5v799Ygws947BZDIbABE/hSEvRd3YF3Z+OaR5KqtSk+6xfleFWPOudUePC9oyHUKNb1CoAAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Our goal is to break this isolation down so that other people can join our NoSQL cluster, our event log, our view servers, and so on.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Here's how it's going to look:\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image8.8cde2a8c.png\",\"height\":1109,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAYElEQVR42hWMUQ4CMRBCeUyzatb7X3T96Oq0tfNCQiAErs919/t8n5k5xwQez5dNsy2HbSDHsFkFrcVWAsdxICptLGNHNKhhNbAKMTfbkKH33uoitCoSxkLS3nx/uaaAP/tqLlP9aXucAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":6},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Each of these internal services are now external services. They have public APIs which anybody can consume. On top of that, anybody can create their own instances of these services.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Our goal is to make it so anybody can contribute to this decentralized backend. That means that we don't just want one NoSQL cluster, or one View server. We want lots of these servers working together. So really it's more like this:\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image9.a51ae040.png\",\"height\":925,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAWklEQVR42j3MWw7AIAhEUfe/1CoYH7RQFEpi0r+bTM4kd8dar3xt21ixQGERM0+6vADGYGaAkEt+WNa2FEJeYeYIFp403S06xUOfvfWmqnTToBE0hiM0+B9HfDbrdEg225UeAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"So how do we make all of these services work together?\"}],\"\\n\",[\"$\",\"$Ld\",null,{\"level\":2,\"id\":\"unifying-the-data-model\",\"children\":\"Unifying the data model\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"We're going to establish a shared data model called the \",[\"$\",\"$Lc\",null,{\"href\":\"/guides/data-repos\",\"children\":\"“user data repository.”\"}]]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image10.6817a213.png\",\"height\":464,\"width\":438,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAV1BMVEX////+/v79/f79/f38/f78/P38/Pz7/P37+/37+/v6+vr5+fn39/f29vb09vr19fT08/Pz8/Pz8/Ly8vLx8fHt8fft8Pbv7+/q7vXr6+vn6/Tl6vTh4eGTCHm6AAAAP0lEQVR42h3GWwJAIBAF0HtRhGLIs/a/zprO1wHh7OJAwPrn9C1zjitrevkkQnPsm2i6O7xBY9KfLg2ncTBgAU0cAiqTWsV8AAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":8},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-72 mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Every data repository contains JSON documents, which we'll call “records”.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image11.4f73b780.png\",\"height\":554,\"width\":838,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAXUlEQVR42i2NSw6AIBBDuf85CRtFHR0HmB8i2kXTtHlpcPfe++dmVmvNOatqGNU11VpjZiKKMf4DHMcJUKYQMaU00KBqG9w70AgiMohlXd/B3JH4LjJfemNFEjN/AF9CdCd3W3PuAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-lg mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"For organizational purposes, we'll bucket these records into “collections.”\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image12.bd8affbd.png\",\"height\":570,\"width\":870,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAXElEQVR42k2KSw4DIQzFcv9zIiQkWPBJSPIytKjdjFe2ZPq8OOcAGGMAoIhYa6mqiPTe55wppZvk7rXW/qO1Jsw5ZzMjIPZWB9TsCrOUUu5NjmeKm8fW4I1/Ip4vSLZznShXgPEAAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-lg mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Now we're going to opinionate our NoSQL services so they all use this \",[\"$\",\"$Lc\",null,{\"href\":\"/guides/data-repos\",\"children\":\"data repository\"}],\" model.\"]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image13.88b82924.png\",\"height\":1010,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAWklEQVR42k3LSQ7AIAxDUe5/0lYqqBCmhCQu7Lqz/PSDAzGl674AtN6oUuvdHEGWPzH+gWplsQA4M4853H3p2qCqAIKaFSpvySKyOdMZB05xLt6FqrU+zQzAB0SLdFQjdjAOAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Remember: the data repo services are still basically NoSQL stores, it's just that they're now organized in a very specific way:\"}],\"\\n\",[\"$\",\"ol\",null,{\"children\":[\"\\n\",[\"$\",\"li\",null,{\"children\":\"Each user has a data repository.\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Each repository has collections.\"}],\"\\n\",[\"$\",\"li\",null,{\"children\":\"Each collection is an ordered K/V store of JSON documents.\"}],\"\\n\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"Since the data repositories can be hosted by anybody, we need to give them \",[\"$\",\"$Lc\",null,{\"href\":\"/specs/at-uri-scheme\",\"children\":\"URLs\"}],\".\"]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image14.47dc55b6.png\",\"height\":354,\"width\":380,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAAAAAAQMlOCAAAAPUlEQVR42h3GSRKAIAwEwPz/m97UcgkkYyhgTNGnFg6DP5PCFseFtrLtHpmup77IDFXcPeO1FPsoE1HdwB+qQDYlhSXO7wAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":7},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-72 mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"While we're at it, let's create a \",[\"$\",\"$Lc\",null,{\"href\":\"/specs/at-uri-scheme\",\"children\":\"whole URL scheme\"}],\" for our records too.\"]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image15.b82b14bb.png\",\"height\":600,\"width\":816,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAa0lEQVR42k1N2w5CIQzj//+TQDwQGWyUs83o9Mk+9JK0aXr/wd3NrPcuIklV55wAIowxmDnnTEQJ5zyuawwKtNYA1Fqj+l0cwIJP6N5bSim8ViyM1gkW2OT7Vn+SbGgKyywsAmCu9Tvb7q8Pd/yJ9ooP5yYAAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":6},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-lg mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Great! Also, since we're going to be syncing these records around the Internet, it would be a good idea to cryptographically sign them so that we know they're authentic.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image16.1d02511f.png\",\"height\":964,\"width\":1510,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAYUlEQVR42h2KQRIDIQgE/f87s0kuWgKCCBrC7hy6emqm/J5ExDln701EtVUQKO7eexcRAEDE9NfnqqMVM8syxkCEvKe/v1e7B99TEye5zIm4dVDTspYBMvNEEhrCMlU9Iv4SgHLskeoJcwAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-lg mx-auto\"}]}],\"\\n\",[\"$\",\"$Ld\",null,{\"level\":2,\"id\":\"charting-the-flow-of-data\",\"children\":\"Charting the flow of data\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Now that we've set up our high-scale decentralized backend, let's map out how an application actually works on ATProto.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Since we're making a new app, we're going to want two things: an app server (which hosts our API \u0026 frontend) and a view server (which collects data from the network for us). We often bundle the app \u0026 view servers, and so we can just call it an “Appview.” Let's start there:\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image17.bdea24e3.png\",\"height\":876,\"width\":1294,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAW0lEQVR42i2LUQ4EIQxCvf9B/TTjqlXbQre7GUJCXoBiblNm//Qpyx2a7IiIoqZjjNbaErnqa+u5/itA5EivgoxIvyoM5qPWCmIfk2NL1MHy5/30x+COFDNIfgGD+3T9ld4JmAAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-md mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"A user logs into our app using OAuth. In the process, they tell us which server hosts their data repository, \",[\"$\",\"em\",null,{\"children\":\"and\"}],\" they give us permission to read and write to it.\"]}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image18.5b6c97f8.png\",\"height\":1338,\"width\":1256,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAdVBMVEX////+//7+/v7+/v39/v79/v39/f79/f38/f78/fz7+/36+/36+/z6+/r6+vr5+vz5+vv5+vn4+vv4+fz4+fv3+fv4+fj3+Pv4+Pj2+Pv29/v39/f29/b09vr19vXz9PPx8vHx8fHw8fDt7e3s7ezi4+Pi4eJ1qSfCAAAASklEQVR42mNgFFTh5eDhYmJgkFEV5JYUBTJYxVgZQIBTXllBQFichYFTRlGOh1+EhYGVU5qJGSTFIqHGJyQlK8zAwqvEy87ExgIAfiUDfs6AUBQAAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":8},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-md mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"We're off to a good start — we can read and write JSON documents in the user's repo. If they already have data from other apps (like a profile) we can read that data too. If we were building a singleplayer app, we'd already be done.\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"But let's chart what happens when we write a JSON document.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image19.6cb5a9de.png\",\"height\":1324,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAHCAMAAAACh/xsAAAAY1BMVEX///////7+/////v7+/v78/f3//Pr8/fz9/Pv8/P37/P37/Pv6+/36+/z6+/r5+/z5+vn7+fj4+fz5+fn4+fn3+fv39/b49fP09fr09fTx8vH08O/v8O/u7+7r7Ozr7Ovr6+uIN7tXAAAAP0lEQVR42g3BhwGAIAwEwBdeIwoaC/bC/lPCHQzaV7tA2Gn/1c9Ec586oDDYviseDpay9BUKStIxBhDyrLV3GVQ9Apu3Vv1yAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":7},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-xl mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"This commits the document to the repo, then fires off a write into the event logs which are listening to the repo.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image20.032fb864.png\",\"height\":827,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r+mnAAAASklEQVR42k2LQQqAQAwD9///VG8K267aTVPNRTAEQhimXcH9ONZtuYfFOWw4kmqrKgARQeqnuUHDas8vyXT3CSEZXwRIdusSJvgCNildvhTsbE0AAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":4},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"From there, the event gets sent to any view services that are listening — including our own!\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image21.d3fed181.png\",\"height\":846,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAECAIAAAA8r+mnAAAATUlEQVR42k2MMQ6AMAwD+/+PwkARoim0tpuQEcmTT3flnTpq3fYtIrTUrEkrVzxijGHXGQKBBFT+Xty9P93sBqYIMwPFNLJAakL/FLg+K8ZdYbqPde8AAAAASUVORK5CYII=\",\"blurWidth\":8,\"blurHeight\":4},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Why are we listening to the event stream if we're the one making the write? Because we're not the only ones making writes! There are lots of user repos generating events, and lots of apps writing to them!\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image22.da5edbc6.png\",\"height\":1250,\"width\":950,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAICAIAAABVpBlvAAAAcUlEQVR42h2OSwoDMQxDc/9TtoW2s+gnP8d2bJlJBqTN4wmUzKy0WloZIqI+zRMAZZLRHHAEEImFc/4TtVrLNEREElWiyqN36jqxLXN/3G/H+znNieeFzI7j9ft+FmKxPVwVaqNlc6xsBOA6UZcl6kCcTW2LLBxKtdsAAAAASUVORK5CYII=\",\"blurWidth\":6,\"blurHeight\":8},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-lg mx-auto\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"So we can see a kind of circular data flow throughout our decentralized backend, with writes being committed to the data repos, then emitted through the event logs into the view servers, where they can be read by our applications.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image23.5342b3db.png\",\"height\":1013,\"width\":1600,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAFCAIAAAD38zoCAAAAXklEQVR42hXJQQLDIAgEQP//0tjmYBSiIOzWXmcKwO99X59KUl8dMkQ1E8U3TtR6MUNHFz0j5lkI+Habiu176rIVsUmWiHhGb70dNWmPdHf7B4BlPpeBjIS+KzJJ/gA5BnQqIAQx8QAAAABJRU5ErkJggg==\",\"blurWidth\":8,\"blurHeight\":5},\"alt\":\"\",\"className\":\"w-full dark:invert max-w-full\"}]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"And (one hopes) that this network continues to scale: not just to add capacity, but to create a wider variety of applications sharing in this open applications network.\"}],\"\\n\",[\"$\",\"div\",null,{\"className\":\"max-w-2xl lg:max-w-3xl\",\"children\":[\"$\",\"$Le\",null,{\"src\":{\"src\":\"/_next/static/media/image24.2111e734.png\",\"height\":1082,\"width\":1504,\"blurDataURL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAGCAIAAABxZ0isAAAAbElEQVR42iWNWw4CMQhFu/9lql9OxvRBKRdaKlUSwoWTHNLee62VcyYiKATYv0rR9+d+vJ6NWoBci0AOKLUEeF9Xp6YYFKM3EUk2jQd37sKk3CIMGSE/KjOrdE6qiDDnPCp3//8HoGYhiNXdv66Qi2fhMIkOAAAAAElFTkSuQmCC\",\"blurWidth\":8,\"blurHeight\":6},\"className\":\"w-full dark:invert max-w-2xl\"}]}],\"\\n\",[\"$\",\"$Ld\",null,{\"level\":2,\"id\":\"building-practical-open-systems\",\"children\":\"Building practical open systems\"}],\"\\n\",[\"$\",\"p\",null,{\"children\":[\"The AT Protocol merges p2p tech with high-scale systems practices. Our founding engineers were core \",[\"$\",\"$Lc\",null,{\"href\":\"https://en.wikipedia.org/wiki/InterPlanetary_File_System\",\"children\":\"IPFS\"}],\" and \",[\"$\",\"$Lc\",null,{\"href\":\"https://en.wikipedia.org/wiki/Dat_(software)\",\"children\":\"Dat\"}],\" engineers, and Martin Kleppmann — the author of \",[\"$\",\"$Lc\",null,{\"href\":\"https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/\",\"children\":\"Data Intensive Applications\"}],\" — is an active technical advisor.\"]}],\"\\n\",[\"$\",\"p\",null,{\"children\":\"Before Bluesky was started, we established a clear requirement of “no steps backwards.” We wanted the network to feel as convenient and global as every social app before it, while still working as an open network. This is why, when we looked at federation and blockchains, the scaling limits of those architectures stood out to us. Our solution was to take standard practices for high scale backends, and then apply the techniques we used in peer-to-peer systems to create an open network.\"}],\"\\n\",[\"$\",\"$Lf\",null,{\"href\":\"/\",\"title\":\"Ready to learn more?\",\"description\":\"Specs, guides, and SDKs can be found here.\"}]]}]}]\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"ATProto for distributed systems engineers - AT Protocol\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering.\"}],[\"$\",\"meta\",\"4\",{\"property\":\"og:title\",\"content\":\"ATProto for distributed systems engineers - AT Protocol\"}],[\"$\",\"meta\",\"5\",{\"property\":\"og:description\",\"content\":\"AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering.\"}],[\"$\",\"meta\",\"6\",{\"property\":\"og:url\",\"content\":\"https://atproto.com/\"}],[\"$\",\"meta\",\"7\",{\"property\":\"og:site_name\",\"content\":\"AT Protocol\"}],[\"$\",\"meta\",\"8\",{\"property\":\"og:image\",\"content\":\"https://atproto.com/default-social-card.png\"}],[\"$\",\"meta\",\"9\",{\"property\":\"og:image:secure_url\",\"content\":\"https://atproto.com/default-social-card.png\"}],[\"$\",\"meta\",\"10\",{\"property\":\"og:image:width\",\"content\":\"1200\"}],[\"$\",\"meta\",\"11\",{\"property\":\"og:image:height\",\"content\":\"630\"}],[\"$\",\"meta\",\"12\",{\"property\":\"og:type\",\"content\":\"website\"}],[\"$\",\"meta\",\"13\",{\"name\":\"twitter:card\",\"content\":\"summary_large_image\"}],[\"$\",\"meta\",\"14\",{\"name\":\"twitter:title\",\"content\":\"ATProto for distributed systems engineers - AT Protocol\"}],[\"$\",\"meta\",\"15\",{\"name\":\"twitter:description\",\"content\":\"AT Protocol is the tech developed at Bluesky for open social networking. In this article we're going to explore AT Proto from the perspective of distributed backend engineering.\"}],[\"$\",\"meta\",\"16\",{\"name\":\"twitter:image\",\"content\":\"https://atproto.com/default-social-card.png\"}]]\n3:null\n"])</script><script>self.__next_f.push([1,"10:I[5552,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"369\",\"static/chunks/369-a583bbb81b96a7d8.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"60\",\"static/chunks/60-3e1824554a9cf3f8.js\",\"203\",\"static/chunks/app/%5Blocale%5D/layout-8706067c5d993875.js\"],\"Providers\"]\n11:I[5211,[\"972\",\"static/chunks/972-f3553dcbba031b91.js\",\"135\",\"static/chunks/135-8f28b0a70a96c388.js\",\"28\",\"static/chunks/28-313384c6289a00c0.js\",\"369\",\"static/chunks/369-a583bbb81b96a7d8.js\",\"212\",\"static/chunks/212-67e821188dcd5fb8.js\",\"60\",\"static/chunks/60-3e1824554a9cf3f8.js\",\"203\",\"static/chunks/app/%5Blocale%5D/layout-8706067c5d993875.js\"],\"Layout\"]\n"])</script><script>self.__next_f.push([1,"8:[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"h-full\",\"suppressHydrationWarning\":true,\"children\":[\"$\",\"body\",null,{\"className\":\"flex min-h-full bg-white antialiased dark:bg-zinc-900\",\"children\":[\"$\",\"$L10\",null,{\"children\":[\"$\",\"div\",null,{\"className\":\"w-full\",\"children\":[\"$\",\"$L11\",null,{\"allSections\":{\"/en.mdx\":[],\"/ja.mdx\":[],\"/pt.mdx\":[],\"/explorer\":[],\"/sdks\":[{\"title\":\"Official libraries\",\"id\":\"official-libraries\"},{\"title\":\"Community libraries\",\"id\":\"community-libraries\"}],\"/articles/atproto-for-distsys-engineers/en.mdx\":[{\"title\":\"Scaling the traditional Web backend\",\"id\":\"scaling-the-traditional-web-backend\"},{\"title\":\"Decentralizing our high-scale backend\",\"id\":\"decentralizing-our-high-scale-backend\"},{\"title\":\"Unifying the data model\",\"id\":\"unifying-the-data-model\"},{\"title\":\"Charting the flow of data\",\"id\":\"charting-the-flow-of-data\"},{\"title\":\"Building practical open systems\",\"id\":\"building-practical-open-systems\"}],\"/articles/atproto-for-distsys-engineers/ja.mdx\":[{\"title\":\"従来の Web バックエンドのスケーリング\",\"id\":\"web\"},{\"title\":\"大規模バックエンドの分散化\",\"id\":\"\"},{\"title\":\"データ モデルの統合\",\"id\":\"\"},{\"title\":\"データの流れを図に表す\",\"id\":\"\"},{\"title\":\"実用的なオープン システムの構築\",\"id\":\"\"}],\"/articles/atproto-for-distsys-engineers/pt.mdx\":[{\"title\":\"Escalando o backend tradicional da Web\",\"id\":\"escalando-o-backend-tradicional-da-web\"},{\"title\":\"Descentralizando nosso backend de alta escala\",\"id\":\"descentralizando-nosso-backend-de-alta-escala\"},{\"title\":\"Unificando o modelo de dados\",\"id\":\"unificando-o-modelo-de-dados\"},{\"title\":\"Mapeando o fluxo de dados\",\"id\":\"mapeando-o-fluxo-de-dados\"},{\"title\":\"Construindo sistemas abertos práticos\",\"id\":\"construindo-sistemas-abertos-praticos\"}],\"/articles/why-atproto\":[],\"/guides/applications/en.mdx\":[{\"title\":\"Introduction\",\"id\":\"introduction\"},{\"title\":\"Step 1. Starting with our ExpressJS app\",\"id\":\"step-1-starting-with-our-express-js-app\"},{\"title\":\"Step 2. Signing in with OAuth\",\"id\":\"step-2-signing-in-with-o-auth\"},{\"title\":\"Step 3. Fetching the user's profile\",\"id\":\"step-3-fetching-the-users-profile\"},{\"title\":\"Step 4. Reading \u0026 writing records\",\"id\":\"step-4-reading-and-writing-records\"},{\"title\":\"Step 5. Creating a custom \\\"status\\\" schema\",\"id\":\"step-5-creating-a-custom-status-schema\"},{\"title\":\"Step 6. Listening to the firehose\",\"id\":\"step-6-listening-to-the-firehose\"},{\"title\":\"Step 7. Listing the latest statuses\",\"id\":\"step-7-listing-the-latest-statuses\"},{\"title\":\"Step 8. Optimistic updates\",\"id\":\"step-8-optimistic-updates\"},{\"title\":\"Thinking in AT Proto\",\"id\":\"thinking-in-at-proto\"},{\"title\":\"Next steps\",\"id\":\"next-steps\"}],\"/guides/applications/ja.mdx\":[{\"title\":\"はじめに\",\"id\":\"\"},{\"title\":\"ステップ 1. ExpressJS アプリから始める\",\"id\":\"1-express-js\"},{\"title\":\"ステップ 2. OAuth でサインイン\",\"id\":\"2-o-auth\"},{\"title\":\"ステップ 3. ユーザーのプロファイルを取得する\",\"id\":\"3\"},{\"title\":\"ステップ 4. レコードの読み取りと書き込み\",\"id\":\"4\"},{\"title\":\"ステップ 5. カスタムの「ステータス」スキーマの作成\",\"id\":\"5\"},{\"title\":\"ステップ 6. ファイアホースをリッスン\",\"id\":\"6\"},{\"title\":\"ステップ 7. 最新のステータスを一覧表示する\",\"id\":\"7\"},{\"title\":\"ステップ 8. 楽観的更新\",\"id\":\"8\"},{\"title\":\"AT Proto で考える\",\"id\":\"at-proto\"},{\"title\":\"次のステップ\",\"id\":\"\"}],\"/guides/applications/pt.mdx\":[{\"title\":\"Introdução\",\"id\":\"introducao\"},{\"title\":\"Etapa 1. Começando com nosso aplicativo ExpressJS\",\"id\":\"etapa-1-comecando-com-nosso-aplicativo-express-js\"},{\"title\":\"Etapa 2. Entrando com OAuth\",\"id\":\"etapa-2-entrando-com-o-auth\"},{\"title\":\"Etapa 3. Obtendo o perfil do usuário\",\"id\":\"etapa-3-obtendo-o-perfil-do-usuario\"},{\"title\":\"Etapa 4. Lendo e escrevendo registros\",\"id\":\"etapa-4-lendo-e-escrevendo-registros\"},{\"title\":\"Etapa 5. Criando um esquema de \\\"status\\\" personalizado\",\"id\":\"etapa-5-criando-um-esquema-de-status-personalizado\"},{\"title\":\"Etapa 6. Ouvindo o firehose\",\"id\":\"etapa-6-ouvindo-o-firehose\"},{\"title\":\"Etapa 7. Listando os status mais recentes\",\"id\":\"etapa-7-listando-os-status-mais-recentes\"},{\"title\":\"Etapa 8. Atualizações otimistas\",\"id\":\"etapa-8-atualizacoes-otimistas\"},{\"title\":\"Pensando em AT Proto\",\"id\":\"pensando-em-at-proto\"},{\"title\":\"Próximos passos\",\"id\":\"proximos-passos\"}],\"/guides/data-repos/en.mdx\":[{\"title\":\"Data Layout\",\"id\":\"data-layout\"},{\"title\":\"Identifier Types\",\"id\":\"identifier-types\"}],\"/guides/data-repos/ja.mdx\":[{\"title\":\"データ レイアウト\",\"id\":\"\"},{\"title\":\"識別子の種類\",\"id\":\"\"}],\"/guides/data-repos/pt.mdx\":[{\"title\":\"Layout de dados\",\"id\":\"layout-de-dados\"},{\"title\":\"Tipos de Identificadores\",\"id\":\"tipos-de-identificadores\"}],\"/guides/faq/en.mdx\":[{\"title\":\"Is the AT Protocol a blockchain?\",\"id\":\"is-the-at-protocol-a-blockchain\"},{\"title\":\"Why not use ActivityPub?\",\"id\":\"why-not-use-activity-pub\"},{\"title\":\"Why create Lexicon instead of using JSON-LD or RDF?\",\"id\":\"why-create-lexicon-instead-of-using-json-ld-or-rdf\"},{\"title\":\"What is “XRPC,” and why not use ___?\",\"id\":\"what-is-xrpc-and-why-not-use\"}],\"/guides/faq/ja.mdx\":[{\"title\":\"AT プロトコルはブロックチェーンですか?\",\"id\":\"at\"},{\"title\":\"ActivityPub を使用しないのはなぜですか?\",\"id\":\"activity-pub\"},{\"title\":\"JSON-LD や RDF を使用する代わりに Lexicon を作成する理由\",\"id\":\"json-ld-rdf-lexicon\"},{\"title\":\"「XRPC」とは何ですか。なぜ ___ を使用しないのですか?\",\"id\":\"xrpc\"}],\"/guides/faq/pt.mdx\":[{\"title\":\"O Protocolo AT é uma blockchain?\",\"id\":\"o-protocolo-at-e-uma-blockchain\"},{\"title\":\"Por que não usar o ActivityPub?\",\"id\":\"por-que-nao-usar-o-activity-pub\"},{\"title\":\"Por que criar o Lexicon em vez de usar JSON-LD ou RDF?\",\"id\":\"por-que-criar-o-lexicon-em-vez-de-usar-json-ld-ou-rdf\"},{\"title\":\"O que é “XRPC” e por que não usar ___?\",\"id\":\"o-que-e-xrpc-e-por-que-nao-usar\"}],\"/guides/glossary/en.mdx\":[{\"title\":\"Atmosphere\",\"id\":\"atmosphere\"},{\"title\":\"AT Protocol\",\"id\":\"at-protocol\"},{\"title\":\"PDS (Personal Data Server)\",\"id\":\"pds-personal-data-server\"},{\"title\":\"AppView\",\"id\":\"app-view\"},{\"title\":\"Relay\",\"id\":\"relay\"},{\"title\":\"Lexicon\",\"id\":\"lexicon\"},{\"title\":\"Data Repo\",\"id\":\"data-repo\"},{\"title\":\"Collection\",\"id\":\"collection\"},{\"title\":\"Record\",\"id\":\"record\"},{\"title\":\"Blob\",\"id\":\"blob\"},{\"title\":\"Label\",\"id\":\"label\"},{\"title\":\"Handle\",\"id\":\"handle\"},{\"title\":\"DID (Decentralized ID)\",\"id\":\"did-decentralized-id\"},{\"title\":\"NSID (Namespaced ID)\",\"id\":\"nsid-namespaced-id\"},{\"title\":\"TID (Timestamp ID)\",\"id\":\"tid-timestamp-id\"},{\"title\":\"CID (Content ID)\",\"id\":\"cid-content-id\"},{\"title\":\"DAG-CBOR\",\"id\":\"dag-cbor\"},{\"title\":\"XRPC\",\"id\":\"xrpc\"}],\"/guides/glossary/ja.mdx\":[{\"title\":\"Atmosphere\",\"id\":\"atmosphere\"},{\"title\":\"AT プロトコル\",\"id\":\"at\"},{\"title\":\"PDS (パーソナル データ サーバー)\",\"id\":\"pds\"},{\"title\":\"AppView\",\"id\":\"app-view\"},{\"title\":\"リレー\",\"id\":\"\"},{\"title\":\"Lexicon\",\"id\":\"lexicon\"},{\"title\":\"データ リポジトリ\",\"id\":\"\"},{\"title\":\"コレクション\",\"id\":\"\"},{\"title\":\"レコード\",\"id\":\"\"},{\"title\":\"BLOB\",\"id\":\"blob\"},{\"title\":\"ラベル\",\"id\":\"\"},{\"title\":\"ハンドル\",\"id\":\"\"},{\"title\":\"DID (分散 ID)\",\"id\":\"did-id\"},{\"title\":\"NSID (名前空間 ID)\",\"id\":\"nsid-id\"},{\"title\":\"TID (タイムスタンプ ID)\",\"id\":\"tid-id\"},{\"title\":\"CID (コンテンツ ID)\",\"id\":\"cid-id\"},{\"title\":\"DAG-CBOR\",\"id\":\"dag-cbor\"},{\"title\":\"XRPC\",\"id\":\"xrpc\"}],\"/guides/glossary/pt.mdx\":[{\"title\":\"Atmosphere\",\"id\":\"atmosphere\"},{\"title\":\"AT Protocol\",\"id\":\"at-protocol\"},{\"title\":\"PDS (Personal Data Server)\",\"id\":\"pds-personal-data-server\"},{\"title\":\"AppView\",\"id\":\"app-view\"},{\"title\":\"Relay\",\"id\":\"relay\"},{\"title\":\"Lexicon\",\"id\":\"lexicon\"},{\"title\":\"Data Repo\",\"id\":\"data-repo\"},{\"title\":\"Collection\",\"id\":\"collection\"},{\"title\":\"Record\",\"id\":\"record\"},{\"title\":\"Blob\",\"id\":\"blob\"},{\"title\":\"Label\",\"id\":\"label\"},{\"title\":\"Handle\",\"id\":\"handle\"},{\"title\":\"DID (Decentralized ID)\",\"id\":\"did-decentralized-id\"},{\"title\":\"NSID (Namespaced ID)\",\"id\":\"nsid-namespaced-id\"},{\"title\":\"TID (Timestamp ID)\",\"id\":\"tid-timestamp-id\"},{\"title\":\"CID (Content ID)\",\"id\":\"cid-content-id\"},{\"title\":\"DAG-CBOR\",\"id\":\"dag-cbor\"},{\"title\":\"XRPC\",\"id\":\"xrpc\"}],\"/guides/identity/en.mdx\":[{\"title\":\"Identifiers\",\"id\":\"identifiers\"},{\"title\":\"DID Methods\",\"id\":\"did-methods\"},{\"title\":\"Handle Resolution\",\"id\":\"handle-resolution\"}],\"/guides/identity/ja.mdx\":[{\"title\":\"識別子\",\"id\":\"\"},{\"title\":\"DID メソッド\",\"id\":\"did\"},{\"title\":\"ハンドル解決\",\"id\":\"\"}],\"/guides/identity/pt.mdx\":[{\"title\":\"Identificadores\",\"id\":\"identificadores\"},{\"title\":\"Métodos DID\",\"id\":\"metodos-did\"},{\"title\":\"Resolução de identificadores\",\"id\":\"resolucao-de-identificadores\"}],\"/guides/lexicon/en.mdx\":[{\"title\":\"Why is Lexicon needed?\",\"id\":\"why-is-lexicon-needed\"},{\"title\":\"HTTP API methods\",\"id\":\"http-api-methods\"},{\"title\":\"Record types\",\"id\":\"record-types\"},{\"title\":\"Tokens\",\"id\":\"tokens\"},{\"title\":\"Versioning\",\"id\":\"versioning\"},{\"title\":\"Schema distribution\",\"id\":\"schema-distribution\"}],\"/guides/lexicon/ja.mdx\":[{\"title\":\"Lexicon が必要な理由\",\"id\":\"lexicon\"},{\"title\":\"HTTP API メソッド\",\"id\":\"http-api\"},{\"title\":\"レコード タイプ\",\"id\":\"\"},{\"title\":\"トークン\",\"id\":\"\"},{\"title\":\"バージョン管理\",\"id\":\"\"},{\"title\":\"スキーマの配布\",\"id\":\"\"}],\"/guides/lexicon/pt.mdx\":[{\"title\":\"Por que o Lexicon é necessário?\",\"id\":\"por-que-o-lexicon-e-necessario\"},{\"title\":\"Métodos de API HTTP\",\"id\":\"metodos-de-api-http\"},{\"title\":\"Tipos de registro\",\"id\":\"tipos-de-registro\"},{\"title\":\"Tokens\",\"id\":\"tokens\"},{\"title\":\"Versionamento\",\"id\":\"versionamento\"},{\"title\":\"Distribuição de esquema\",\"id\":\"distribuicao-de-esquema\"}],\"/guides/overview/en.mdx\":[{\"title\":\"Identity\",\"id\":\"identity\"},{\"title\":\"Data repositories\",\"id\":\"data-repositories\"},{\"title\":\"Federation\",\"id\":\"federation\"},{\"title\":\"Interoperation\",\"id\":\"interoperation\"},{\"title\":\"Achieving scale\",\"id\":\"achieving-scale\"},{\"title\":\"Algorithmic choice\",\"id\":\"algorithmic-choice\"},{\"title\":\"Account portability\",\"id\":\"account-portability\"},{\"title\":\"Speech, reach, and moderation\",\"id\":\"speech-reach-and-moderation\"},{\"title\":\"Specifications\",\"id\":\"specifications\"}],\"/guides/overview/ja.mdx\":[{\"title\":\"アイデンティティ\",\"id\":\"\"},{\"title\":\"データ リポジトリ\",\"id\":\"\"},{\"title\":\"フェデレーション\",\"id\":\"\"},{\"title\":\"相互運用\",\"id\":\"\"},{\"title\":\"スケールの実現\",\"id\":\"\"},{\"title\":\"アルゴリズムの選択\",\"id\":\"\"},{\"title\":\"アカウントの移植性\",\"id\":\"\"},{\"title\":\"スピーチ、リーチ、モデレーション\",\"id\":\"\"},{\"title\":\"仕様\",\"id\":\"\"}],\"/guides/overview/pt.mdx\":[{\"title\":\"Identidade\",\"id\":\"identidade\"},{\"title\":\"Repositórios de dados\",\"id\":\"repositorios-de-dados\"},{\"title\":\"Federação\",\"id\":\"federacao\"},{\"title\":\"Interoperação\",\"id\":\"interoperacao\"},{\"title\":\"Alcançando escala\",\"id\":\"alcancando-escala\"},{\"title\":\"Escolha algorítmica\",\"id\":\"escolha-algoritmica\"},{\"title\":\"Portabilidade de conta\",\"id\":\"portabilidade-de-conta\"},{\"title\":\"Fala, alcance e moderação\",\"id\":\"fala-alcance-e-moderacao\"},{\"title\":\"Especificações\",\"id\":\"especificacoes\"}],\"/guides/self-hosting/en.mdx\":[{\"title\":\"Table of Contents\",\"id\":\"table-of-contents\"},{\"title\":\"Preparation for self-hosting PDS\",\"id\":\"preparation-for-self-hosting-pds\"},{\"title\":\"Open your cloud firewall for HTTP and HTTPS\",\"id\":\"open-your-cloud-firewall-for-http-and-https\"},{\"title\":\"Configure DNS for your domain\",\"id\":\"configure-dns-for-your-domain\"},{\"title\":\"Check that DNS is working as expected\",\"id\":\"check-that-dns-is-working-as-expected\"},{\"title\":\"Installer on Ubuntu 20.04/22.04 and Debian 11/12\",\"id\":\"installer-on-ubuntu-20-04-22-04-and-debian-11-12\"},{\"title\":\"Verifying that your PDS is online and accessible\",\"id\":\"verifying-that-your-pds-is-online-and-accessible\"},{\"title\":\"Creating an account using pdsadmin\",\"id\":\"creating-an-account-using-pdsadmin\"},{\"title\":\"Creating an account using an invite code\",\"id\":\"creating-an-account-using-an-invite-code\"},{\"title\":\"Using the Bluesky app with your PDS\",\"id\":\"using-the-bluesky-app-with-your-pds\"},{\"title\":\"Updating your PDS\",\"id\":\"updating-your-pds\"},{\"title\":\"Getting help\",\"id\":\"getting-help\"}],\"/guides/self-hosting/ja.mdx\":[{\"title\":\"目次\",\"id\":\"\"},{\"title\":\"セルフホスティング PDS の準備\",\"id\":\"pds\"},{\"title\":\"クラウド ファイアウォールを HTTP および HTTPS 用に開く\",\"id\":\"http-https\"},{\"title\":\"ドメインの DNS を構成する\",\"id\":\"dns\"},{\"title\":\"DNS が期待どおりに動作していることを確認します\",\"id\":\"dns-2\"},{\"title\":\"Ubuntu 20.04/22.04 および Debian 11/12 のインストーラー\",\"id\":\"ubuntu-20-04-22-04-debian-11-12\"},{\"title\":\"PDS がオンラインでアクセス可能であることを確認する\",\"id\":\"pds-2\"},{\"title\":\"pdsadmin を使用してアカウントを作成する\",\"id\":\"pdsadmin\"},{\"title\":\"招待コードを使用してアカウントを作成する\",\"id\":\"\"},{\"title\":\"PDS で Bluesky アプリを使用する\",\"id\":\"pds-bluesky\"},{\"title\":\"PDS の更新\",\"id\":\"pds-3\"},{\"title\":\"ヘルプの取得\",\"id\":\"\"}],\"/guides/self-hosting/pt.mdx\":[{\"title\":\"Índice\",\"id\":\"indice\"},{\"title\":\"Preparação para auto-hospedagem de PDS\",\"id\":\"preparacao-para-auto-hospedagem-de-pds\"},{\"title\":\"Abra seu firewall de nuvem para HTTP e HTTPS\",\"id\":\"abra-seu-firewall-de-nuvem-para-http-e-https\"},{\"title\":\"Configure o DNS para seu domínio\",\"id\":\"configure-o-dns-para-seu-dominio\"},{\"title\":\"Verifique se o DNS está funcionando conforme o esperado\",\"id\":\"verifique-se-o-dns-esta-funcionando-conforme-o-esperado\"},{\"title\":\"Instalador no Ubuntu 20.04/22.04 e Debian 11/12\",\"id\":\"instalador-no-ubuntu-20-04-22-04-e-debian-11-12\"},{\"title\":\"Verificando se seu PDS está online e acessível\",\"id\":\"verificando-se-seu-pds-esta-online-e-acessivel\"},{\"title\":\"Criando uma conta usando pdsadmin\",\"id\":\"criando-uma-conta-usando-pdsadmin\"},{\"title\":\"Criando uma conta usando um código de convite\",\"id\":\"criando-uma-conta-usando-um-codigo-de-convite\"},{\"title\":\"Usando o aplicativo Bluesky com seu PDS\",\"id\":\"usando-o-aplicativo-bluesky-com-seu-pds\"},{\"title\":\"Atualizando seu PDS\",\"id\":\"atualizando-seu-pds\"},{\"title\":\"Obtendo ajuda\",\"id\":\"obtendo-ajuda\"}],\"/specs/at-uri-scheme\":[],\"/specs/atp\":[{\"title\":\"Protocol Structure\",\"id\":\"protocol-structure\"},{\"title\":\"Protocol Extension and Applications\",\"id\":\"protocol-extension-and-applications\"},{\"title\":\"What Is Missing?\",\"id\":\"what-is-missing\"},{\"title\":\"Future Work\",\"id\":\"future-work\"}],\"/specs/blob\":[{\"title\":\"Blob Metadata\",\"id\":\"blob-metadata\"},{\"title\":\"Blob Lifecycle\",\"id\":\"blob-lifecycle\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/cryptography\":[{\"title\":\"ECDSA Signature Malleability\",\"id\":\"ecdsa-signature-malleability\"},{\"title\":\"Public Key Encoding\",\"id\":\"public-key-encoding\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/data-model\":[{\"title\":\"Relationship With IPLD\",\"id\":\"relationship-with-ipld\"},{\"title\":\"Data Types\",\"id\":\"data-types\"},{\"title\":\"blob Type\",\"id\":\"blob-type\"},{\"title\":\"JSON Representation\",\"id\":\"json-representation\"},{\"title\":\"Link and CID Formats\",\"id\":\"link-and-cid-formats\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security and Privacy Considerations\",\"id\":\"security-and-privacy-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/did\":[{\"title\":\"Blessed DID Methods\",\"id\":\"blessed-did-methods\"},{\"title\":\"AT Protocol DID Identifier Syntax\",\"id\":\"at-protocol-did-identifier-syntax\"},{\"title\":\"DID Documents\",\"id\":\"did-documents\"},{\"title\":\"Representation of Public Keys\",\"id\":\"representation-of-public-keys\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/event-stream\":[{\"title\":\"Streaming Wire Protocol (v0)\",\"id\":\"streaming-wire-protocol-v0\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security and Privacy Considerations\",\"id\":\"security-and-privacy-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/handle\":[{\"title\":\"Handle Identifier Syntax\",\"id\":\"handle-identifier-syntax\"},{\"title\":\"Additional Non-Syntax Restrictions\",\"id\":\"additional-non-syntax-restrictions\"},{\"title\":\"Identifier Examples\",\"id\":\"identifier-examples\"},{\"title\":\"Handle Resolution\",\"id\":\"handle-resolution\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/label\":[{\"title\":\"Schema and Data Model\",\"id\":\"schema-and-data-model\"},{\"title\":\"Value\",\"id\":\"value\"},{\"title\":\"Label Lifecycle: Negation and Expiration\",\"id\":\"label-lifecycle-negation-and-expiration\"},{\"title\":\"Signatures\",\"id\":\"signatures\"},{\"title\":\"Self-Labels in Records\",\"id\":\"self-labels-in-records\"},{\"title\":\"Labeler Service Identity\",\"id\":\"labeler-service-identity\"},{\"title\":\"Label Distribution Endpoints\",\"id\":\"label-distribution-endpoints\"},{\"title\":\"Labeler HTTP Headers\",\"id\":\"labeler-http-headers\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/lexicon\":[{\"title\":\"Overview of Types\",\"id\":\"overview-of-types\"},{\"title\":\"Lexicon Files\",\"id\":\"lexicon-files\"},{\"title\":\"Primary Type Definitions\",\"id\":\"primary-type-definitions\"},{\"title\":\"Field Type Definitions\",\"id\":\"field-type-definitions\"},{\"title\":\"String Formats\",\"id\":\"string-formats\"},{\"title\":\"When to use $type\",\"id\":\"when-to-use-type\"},{\"title\":\"Lexicon Evolution\",\"id\":\"lexicon-evolution\"},{\"title\":\"Authority and Control\",\"id\":\"authority-and-control\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/nsid\":[],\"/specs/oauth\":[{\"title\":\"Clients\",\"id\":\"clients\"},{\"title\":\"Identity Authentication\",\"id\":\"identity-authentication\"},{\"title\":\"Authorization Scopes\",\"id\":\"authorization-scopes\"},{\"title\":\"Authorization Requests\",\"id\":\"authorization-requests\"},{\"title\":\"Tokens and Session Lifetime\",\"id\":\"tokens-and-session-lifetime\"},{\"title\":\"Demonstrating Proof of Possession (DPoP)\",\"id\":\"demonstrating-proof-of-possession-d-po-p\"},{\"title\":\"Authorization Servers\",\"id\":\"authorization-servers\"},{\"title\":\"Summary of Authorization Flow\",\"id\":\"summary-of-authorization-flow\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/record-key\":[],\"/specs/repository\":[{\"title\":\"Repo Data Structure (v3)\",\"id\":\"repo-data-structure-v3\"},{\"title\":\"CAR File Serialization\",\"id\":\"car-file-serialization\"},{\"title\":\"Repository Diffs\",\"id\":\"repository-diffs\"},{\"title\":\"Security Considerations\",\"id\":\"security-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}],\"/specs/xrpc\":[{\"title\":\"Lexicon HTTP Endpoints\",\"id\":\"lexicon-http-endpoints\"},{\"title\":\"Authentication\",\"id\":\"authentication\"},{\"title\":\"Service Proxying\",\"id\":\"service-proxying\"},{\"title\":\"Summary of HTTP Headers\",\"id\":\"summary-of-http-headers\"},{\"title\":\"Summary of HTTP Status Codes\",\"id\":\"summary-of-http-status-codes\"},{\"title\":\"Usage and Implementation Guidelines\",\"id\":\"usage-and-implementation-guidelines\"},{\"title\":\"Security and Privacy Considerations\",\"id\":\"security-and-privacy-considerations\"},{\"title\":\"Possible Future Changes\",\"id\":\"possible-future-changes\"}]},\"children\":[\"$\",\"$L5\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"$6\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"div\",null,{\"className\":\"absolute inset-0 -z-10 mx-0 max-w-none overflow-hidden\",\"children\":[\"$\",\"div\",null,{\"className\":\"absolute left-1/2 top-0 ml-[-38rem] h-[25rem] w-[81.25rem] dark:[mask-image:linear-gradient(white,transparent)]\",\"children\":[\"$\",\"div\",null,{\"className\":\"absolute inset-0 bg-gradient-to-r from-[#6fa5ff] to-[#9dc0fb] opacity-40 [mask-image:radial-gradient(farthest-side_at_top,white,transparent)] dark:from-[#0090f2]/30 dark:to-[#7588ff]/30 dark:opacity-100\",\"children\":[\"$\",\"svg\",null,{\"aria-hidden\":\"true\",\"className\":\"absolute inset-x-0 inset-y-[-50%] h-[200%] w-full fill-black/40 stroke-black/50 mix-blend-overlay dark:fill-white/2.5 dark:stroke-white/5\",\"children\":[[\"$\",\"defs\",null,{\"children\":[\"$\",\"pattern\",null,{\"id\":\":S1:\",\"width\":72,\"height\":56,\"patternUnits\":\"userSpaceOnUse\",\"x\":-12,\"y\":4,\"children\":[\"$\",\"path\",null,{\"d\":\"M.5 56V.5H72\",\"fill\":\"none\"}]}]}],[\"$\",\"rect\",null,{\"width\":\"100%\",\"height\":\"100%\",\"strokeWidth\":0,\"fill\":\"url(#:S1:)\"}],[\"$\",\"svg\",null,{\"x\":-12,\"y\":4,\"className\":\"overflow-visible\",\"children\":[]}]]}]}]}]}],[\"$\",\"div\",null,{\"className\":\"mx-auto flex h-full max-w-xl flex-col items-center justify-center py-16 text-center\",\"children\":[[\"$\",\"p\",null,{\"className\":\"text-sm font-semibold text-zinc-900 dark:text-white\",\"children\":\"404\"}],[\"$\",\"h1\",null,{\"className\":\"mt-2 text-2xl font-bold text-zinc-900 dark:text-white\",\"children\":\"Page not found\"}],[\"$\",\"p\",null,{\"className\":\"mt-2 text-base text-zinc-600 dark:text-zinc-400\",\"children\":\"Sorry, we couldn’t find the page you’re looking for.\"}],[\"$\",\"$Lc\",null,{\"className\":\"inline-flex gap-0.5 justify-center overflow-hidden text-sm font-medium transition rounded-full bg-zinc-900 py-1 px-3 text-white hover:bg-zinc-700 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-1 dark:ring-inset dark:ring-blue-400/20 dark:hover:bg-blue-400/10 dark:hover:text-blue-300 dark:hover:ring-blue-300 mt-8\",\"href\":\"/\",\"children\":[false,\"Back to docs\",[\"$\",\"svg\",null,{\"viewBox\":\"0 0 20 20\",\"fill\":\"none\",\"aria-hidden\":\"true\",\"className\":\"mt-0.5 h-5 w-5 -mr-1\",\"children\":[\"$\",\"path\",null,{\"stroke\":\"currentColor\",\"strokeLinecap\":\"round\",\"strokeLinejoin\":\"round\",\"d\":\"m11.5 6.5 3 3.5m0 0-3 3.5m3-3.5h-9\"}]}]]}]]}]],\"notFoundStyles\":[]}]}]}]}]}]}]\n"])</script></body></html>