Cap’n Proto 1.0
(capnproto.org)- La modalidad opt-in de cancelación de RPC cambió de la llamada
context.allowCancellation()a la anotaciónallowCancellationen el schema - Los usuarios que no usaban
context.allowCancellation()no necesitan cambios al actualizar a 1.0, y la cancelación seguirá deshabilitada por defecto - La versión 1.0 es un lanzamiento de tipo soporte a largo plazo (LTS), y se planea hacer backport de correcciones de errores a la rama 1.0 durante mucho tiempo
- Se aplicaron mejoras de rendimiento en Cap’n Proto RPC, incluyendo menor asignación de memoria en la implementación de RPC y en el framework KJ I/O, omisión de algunos mensajes del protocolo RPC y reducción de syscalls mediante mejoras en el buffering de mensajes pequeños
- KJ usa
kqueue()para el manejo de I/O asíncrono en MacOS y sistemas de la familia BSD, reemplazando el método anterior basado enpoll(), que era más lento - Las implementaciones de cliente y servidor HTTP de KJ ahora soportan el método
CONNECT - Se agregó
capnp::RevocableServer, que ofrece una estructura para ayudar a evitar errores de use-after-free al exportar como wrapper RPC objetos cuyo wrapper no controla el lifetime - El desarrollo de 2.0 avanzará en la nueva rama
v2, y la ramamasterpasará a ser la rama LTS de 1.0 para evitar que los proyectos existentes se rompan por cambios - El plan para 2.0 no incluye cambios incompatibles hacia atrás en el formato de serialización ni en el protocolo RPC, sino cambios centrados en la API de C++ y KJ, por lo que las aplicaciones escritas en otros lenguajes no se verán afectadas
- El plan de 2.0 incluye requerir compiladores C++20 o C++23, usar coroutines de C++20, dejar de soportar modo sin excepciones y modo sin RTTI, y cambios en las APIs de
kj::Maybeykj::AsyncOutputStream
3 comentarios
Oh... esto por fin
Opiniones de Hacker News
Comparto una noticia muy triste para las comunidades de Cap'n Proto y Sandstorm. Ian Denhardt (zenhack en HN), quien fue un colaborador clave de la implementación en Go, falleció repentinamente hace unas semanas; en los últimos 3 años colaboré mucho con él y nos hicimos amigos, así que siento una gran pérdida.
En la práctica, como líder del proyecto, ahora tengo que llenar el enorme vacío que dejó Ian, así que si puedes contribuir al proyecto, la ayuda es muy necesaria. Si eres colaborador o maintainer de otras implementaciones, como C++, Rust, etc., me encantaría que te pusieras en contacto. Para continuar el trabajo de Ian, necesitamos trabajar con personas muy inteligentes.
RIP Ian, aprendí muchísimo trabajando contigo. Contacto: https://github.com/lthibault, https://matrix.to/#/#go-capnp:matrix.org, Telegram @lthibault, gmail louist87
No es que le dé la espalda al futuro, pero me ha llevado a intentar disfrutar más las pequeñas cosas de la vida. Ian dejó un legado increíble que llegó a muchísima gente. RIP Ian.
Sorprende que haya tan pocos protocolos además de Cap'n Proto que tengan promise pipelining. El único otro ejemplo que se me ocurre es 9p, pero 9p no es un protocolo de propósito general
https://capnproto.org/news/2013-12-13-promise-pipelining-cap...
Aun así, parece una función que debería usarse más ampliamente
Por ejemplo, si el cliente Joe llama al Service A y pasa el resultado prometido a Service B, Service B tiene que hacer una llamada remota a Service A para obtener el resultado de esa promesa
En ese momento, los límites de seguridad se complican de inmediato. También está el problema de qué hacer con el modelo de delegación, pero lo más grave es que desaparece la contrapresión. Si el cliente Joe crea miles de llamadas a Service A y pasa a Service B resultados que todavía no se han materializado, Service A puede verse inundado como en un DDoS y Service B puede agotar el tiempo de espera
Sin embargo, el promise pipelining también requiere soporte del cliente. Me pregunto si existe algún cliente 9p que, cuando el usuario emite varios walk, los envíe todos sin esperar la respuesta del primer walk
Además, 9p solo ofrece promise pipelining para descriptores de archivo. Con eso ya se puede hacer mucho, pero si quieres leer todos los archivos de un directorio, tendrías que poder emitir un read y luego hacer walk sobre el resultado de ese read, y parece que 9p no lo soporta. En rsyscall, mi biblioteca de protocolo para llamadas remotas al sistema, de hecho sí lo soporto
Recuerdo que Mark Miller escribió más tarde la propuesta de promises para JavaScript. Sin embargo, la extensión RPC no se concretó y, en su lugar, obtuvimos async/await, que no parece encajar tan bien con el pipelining
Spritely Goblins y OCapN, intentos más recientes de crear un sistema de capacidades distribuidas al estilo de E, también buscan el pipelining, así que si estás en cap-talk probablemente puedas enterarte de algunos protocolos similares más, aunque haya pocos casos de uso reales
Dicho eso, aunque es una función genial, el promise pipelining parece requerir programar con promises explícitas reales, y ya se sabe bien lo complicado que eso puede llegar a ser
En el lado opuesto, es decir, el de los “objetos activos” con bucles de eventos y concurrencia cooperativa, una idea interesante y menos conocida son los causality IDs de DCOM/COM+. Son un medio para controlar la reentrancia; se pueden ver CoGetCurrentLogicalThreadId en la documentación de Microsoft y la discusión de CALLTYPE_TOPLEVEL_CALLPENDING en Effective COM. Más tarde parece que intentaron venderlo como una función nueva en ASTA de Win8/UWP
[1] http://erights.org/elib/distrib/captp/index.html
[2] http://erights.org/talks/thesis/index.html
[3] https://spritely.institute/goblins/
[4] https://github.com/ocapn/ocapn
[5] https://groups.google.com/g/captalk/
[6] https://learn.microsoft.com/openspecs/windows_protocols/ms-d...
[7] https://learn.microsoft.com/windows/win32/api/combaseapi/nf-...
[8] https://archive.org/details/effectivecom50wa00boxd/page/150
[9] https://devblogs.microsoft.com/oldnewthing/20210224-00/?p=10...
Lo segundo parece posible de alguna manera, pero lo primero depende de cosas como la cantidad de valores en la lista de resultados
Felicitaciones por el lanzamiento. Después de 10 años, de verdad debe ser muy emocionante. Me pregunto si habrá más trabajo en implementaciones para otros lenguajes
La idea del formato me gusta mucho, pero los lenguajes principales de nuestro stack no tienen un soporte que me dé confianza para usarlo en producto
Lamentablemente, es difícil prometer algo nuevo al respecto. El trabajo en Cap'n Proto está impulsado por las necesidades de mi proyecto principal, el runtime de Cloudflare Workers, y eso es principalmente C++. También interactúa con servicios en Go y Rust, y esas implementaciones parecen cumplir su función ahí
Dicho de otra forma, Cap'n Proto es un proyecto open source y espero que sea útil para la gente, pero no es un producto que esté vendiendo, así que no me enfoco especialmente en lograr que todos lo adopten. Como siempre, las contribuciones son bienvenidas
Un caso en el que podría haber un gran cambio sería si Cloudflare decidiera convertir Cap'n Proto en una funcionalidad pública de la plataforma Workers. Entonces habría una necesidad directa de pulirlo mucho en varios lenguajes. A veces lo conversamos, pero por ahora no hay planes
Si quieres intentarlo, adelante. Es bastante probable que la gente colabore. Solo hay que tener claro que simplemente es muchísimo trabajo
Para agregar contexto, Kenton operó durante años el sistema proto interno de Google y, después de irse, creó su propia versión open source
Si hay alguien de Cloudflare que haya participado en la elección de Cap'n Proto por sobre otras opciones como protobuf, me gustaría saber qué consideraciones hubo. Quisiera entender si esas razones también son importantes para mí, o si no hace falta preocuparse a menos que uno maneje una escala enorme
Incluso hoy, el pipeline de datos de Cloudflare, es decir, el sistema que genera logs y analítica en el edge, se basa en gran medida en la serialización de Cap'n Proto. Yo personalmente no participé mucho en ese proyecto
En el caso de Cloudflare Workers, como yo inicié el proyecto, usé lo mío. Probablemente no sea la explicación que querías
Aun así, creo que el protocolo RPC de Cap'n Proto es mucho más expresivo que las alternativas, y eso ayudó mucho a implementar el sandboxing del Workers Runtime y funcionalidades de sistemas distribuidos como Durable Objects. https://blog.cloudflare.com/introducing-workers-durable-obje...
Si eliges datos con una forma más cercana a registros, no orientados a columnas como Parquet, y quieres un formato con tiempo de parsing previo prácticamente cero, las opciones se reducen bastante. Si al leer cambias valores de campos, puede haber marshaling
En https://capnproto.org/news/2014-06-17-capnproto-flatbuffers-... se tratan estos trade-offs
Cap'n Proto se creó originalmente para https://sandstorm.io/, y el trabajo que Kenton habría hecho después de ser contratado por Cloudflare probablemente terminó llevando a Cloudflare Workers
Otra consideración: https://github.com/google/flatbuffers/issues/2#issuecomment-...
Tengo entendido que Albert Strasheim, el ingeniero clave que construyó todo eso, hizo benchmarks de las principales opciones de serialización y obtuvo como resultado que Cap'n Proto era bastante más rápido que protobuf
Había una excelente implementación en C++ y se podía usar con nginx. Si mal no recuerdo, también había algo de Lua involucrado. La implementación en Go que se usaba del lado del consumo tenía partes ásperas, pero la gente pudo arreglar las partes centrales que necesitaban
Cloudflare siempre le dio importancia a la eficiencia de costos de las máquinas, así que, considerando los requisitos de rendimiento de la época, fue una elección natural. Durante mi tiempo en el equipo de datos, Cap'n Proto fue bastante fácil de usar, y compartir las definiciones proto desde un repositorio central de esquemas también funcionó bien
Me entusiasma que Cap'n Proto participe en el trabajo de estandarización de OCapN. Me pregunto si esto será parte del trabajo de Cap'n Proto 2.0
https://github.com/ocapn/ocapn
Me gusta OCapN, pero con todas las cosas que tengo a cargo ahora, no sé cuánto tiempo podré dedicarle personalmente. Ojalá pudiera dar mejores noticias, pero es una pérdida trágica para todos nosotros
Nunca he usado Cap'n Proto, pero agradezco la muy útil respuesta de kentonv en la FAQ [1], donde explica por qué los campos obligatorios son un problema en los protocolos.
La enlazo seguido cada vez que alguien pregunta por qué protobuf 3 no tiene campos required.
[1] https://capnproto.org/faq.html#how-do-i-make-a-field-require...
Por lo tanto, el mantra tan repetido de “hacer que los estados ilegales sean imposibles de representar” no aplica aquí.
https://github.com/stepchowfun/typical#asymmetric-fields-can...
Para agregar y eliminar campos obligatorios de forma segura, Typical ofrece asymmetric, un estado intermedio entre los campos opcionales y los obligatorios. Un campo asymmetric de una estructura se considera obligatorio para quien escribe y opcional para quien lee. A diferencia de un campo opcional, un campo asymmetric puede promoverse de forma segura a obligatorio, o revertirse en sentido contrario.
Me habría gustado que hubiera un parámetro para activar y desactivar la validación de mensajes obligatorios, en lugar de depender del código de la aplicación. Podría desactivarse dentro de la aplicación o en el bus de mensajes, pero en general, tenerla activada sería de gran ayuda para los desarrolladores.
[1] https://capnproto.org/faq.html#how-do-i-make-a-field-require...
Algo que realmente me gusta de las interfaces de Go es que puedo tipar remotamente solo lo que uso y omitir el resto. No es divertido que un servicio falle por cambios de contrato que ni siquiera usa, y además es difícil de encontrar.
Es un gran logro. Si soy sincero, no recomendaría Cap'n Proto. La API de C++ es muy incómoda.
El beneficio del parsing sin copias también es menor de lo que esperaba. Como la usabilidad es incómoda, es poco probable que quieras mantener los datos dentro de las estructuras de datos de Cap'n Proto, y en el 99% de los casos terminas copiándolos a tus propias estructuras.
El mundo, en general, se ha asentado en Protobuf para este tipo de implementaciones binarias, así que también hay más fricción con otros sistemas.
Yo solo lo he usado para serialización. Tal vez la parte de RPC sea más atractiva.
Me habría encantado que Thrift hubiera triunfado en lugar de Protobuf/gRPC. Tenía un diseño mucho mejor y más flexible que cualquier cosa que haya visto antes o después. Creo que desapareció principalmente por su pésima documentación, y quizá también porque no tenía un gran nombre detrás.
Siempre quise agregar a Cap'n Proto una API alternativa opcional que usara “estructuras C normales”, o algo cercano, para casos de uso donde no hacer copias no importa demasiado, y que ofreciera serialización con una copia al estilo protobuf, pero todavía no lo he hecho.
Dicho eso, personalmente siempre me ha interesado mucho más el protocolo RPC que la serialización. Creo que el protocolo RPC sí es un cambio de paradigma real para casi cualquier caso de uso no trivial.
En cualquier caso, los principales problemas de la serialización binaria son el versionado de esquemas y mensajes, y la codificación delta. Si ignoras esas dos cosas, la serialización binaria plana es trivial.
No hay ninguna biblioteca que ofrezca una solución que maneje bien ambas cosas.
Siempre me gustó la idea de capnp, pero al final me molesta que, siendo un protocolo de codificación de mensajes, tenga opiniones incluso sobre cómo debería estructurar la arquitectura de mi servidor.
gRPC sin duda también tiene este problema, pero está separado de protobuf de forma muy clara, aunque protobuf sí tiene algunas funcionalidades relacionadas con gRPC.
Por ese acoplamiento, cada vez que lo comparo con capnp termino inclinándome por flatbuffers o protobuf. En especial porque las implementaciones de fb y pb son mucho más simples, y valoro mucho la simplicidad desde el punto de vista de seguridad y mantenimiento.
La falta de buenas implementaciones de terceros en otros lenguajes también me parece una señal directa de que ese juicio es válido. El bus factor y la supervivencia a largo plazo también son muy malos. La simplicidad manda.
La versión de Rust también exige su propio estilo de vida normativo. Mucho de eso tiene sentido dentro de una visión de mundo de cero copias, pero aun así dificultaba muchas expresiones y la documentación también era difícil de leer.
Por eso, solo por esa razón, normalmente elegiría flatbuffers.
Aun así, espero que algún día llegue una situación en la que cap'n'proto sea necesario y útil, y quisiera terminar al menos uno de los varios proyectos personales que he fork-eado durante años para probarlo. Es cierto que tiene ingeniería de alta calidad.
No es nuevo, pero me pregunto si el nombre es un homenaje intencional a Star Trek Voyager, o si hay alguna otra referencia que desconozco.
https://memory-alpha.fandom.com/wiki/Captain_Proton
El nombre Cap'n Proto originalmente significaba Capabilities and Protobufs. Era un protocolo RPC basado en capacidades y construido sobre Protocol Buffers. Pero al principio me dieron ganas de probar un formato de serialización completamente distinto. Aun así, “Proto” seguía encajando como referencia a protocolo, así que mantuve el nombre.
El juego de palabras “cerealization protocol” en realidad me lo contó otra persona, pero lo agregué al logo de inmediato.
Parece que es Serialization/RPC, así que probablemente sea un error tipográfico.