[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-29-semaphores-async-rust-deadlocks-fire-and-forget":3},{"article":4,"author":51},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":31,"related_articles":32},"d6000000-0000-0000-0000-000000000301","a0000000-0000-0000-0000-000000000066","Deep EVM #29 : Sémaphores en Rust async — Chasse aux deadlocks et patterns fire-and-forget","deep-evm-29-semaphores-async-rust-deadlocks-fire-and-forget","Plongée approfondie dans tokio::sync::Semaphore pour le contrôle de contrepression, les patterns d'écriture fire-and-forget, le diagnostic de deadlocks avec tracing et tokio-console, et les solutions de production avec les permits RAII et les timeouts d'acquisition.","## Quand la concurrence rencontre les limites de ressources\n\nQuand vous exécutez un pipeline à haut débit — un bot MEV traitant 180 000 chaînes d'arbitrage par bloc, un serveur API gérant 10 000 requêtes simultanées, ou un job ETL écrivant des millions de lignes — vous finissez inévitablement par atteindre un plafond de ressources. Les pools de connexions à la base de données s'épuisent. Les fournisseurs RPC vous limitent en débit. La mémoire gonfle parce que vous avez créé 50 000 tâches tokio, chacune détenant les données d'une chaîne entière.\n\nLe sémaphore est la primitive qui résout ce problème : il limite le nombre d'opérations concurrentes à un maximum donné.\n\n## tokio::sync::Semaphore : les bases\n\n```rust\nuse tokio::sync::Semaphore;\nuse std::sync::Arc;\n\nlet semaphore = Arc::new(Semaphore::new(10)); \u002F\u002F Maximum 10 opérations concurrentes\n\nfor item in items {\n    let permit = semaphore.clone().acquire_owned().await.unwrap();\n    tokio::spawn(async move {\n        process(item).await;\n        drop(permit); \u002F\u002F Libérer le permit\n    });\n}\n```\n\n`acquire_owned()` attend qu'un permit soit disponible, puis le retourne. Le permit est un guard RAII — il est automatiquement libéré quand il est droppé. Cela garantit que le sémaphore ne fuit jamais, même en cas de panique.\n\n### Permits RAII : pourquoi c'est important\n\nLe pattern RAII (Resource Acquisition Is Initialization) est crucial pour les sémaphores. Comparez :\n\n```rust\n\u002F\u002F DANGEREUX : le permit peut fuir si process() panique\nlet permit = semaphore.acquire().await.unwrap();\nprocess(item).await;\npermit.forget(); \u002F\u002F Oubli intentionnel — rare mais parfois nécessaire\n\n\u002F\u002F SÛR : le permit est libéré même en cas de panique\nlet _permit = semaphore.acquire().await.unwrap();\nprocess(item).await;\n\u002F\u002F _permit droppé automatiquement ici\n```\n\n## Le pattern fire-and-forget\n\nDans les systèmes de production, vous avez souvent besoin de lancer des opérations d'écriture sans attendre leur achèvement :\n\n```rust\nstruct FireAndForgetWriter {\n    semaphore: Arc\u003CSemaphore>,\n    db_pool: PgPool,\n}\n\nimpl FireAndForgetWriter {\n    fn new(max_concurrent: usize, db_pool: PgPool) -> Self {\n        Self {\n            semaphore: Arc::new(Semaphore::new(max_concurrent)),\n            db_pool,\n        }\n    }\n\n    fn write(&self, data: Vec\u003CRecord>) {\n        let semaphore = self.semaphore.clone();\n        let pool = self.db_pool.clone();\n\n        tokio::spawn(async move {\n            \u002F\u002F Acquérir avec timeout pour éviter les deadlocks\n            let permit = match tokio::time::timeout(\n                Duration::from_secs(30),\n                semaphore.acquire_owned()\n            ).await {\n                Ok(Ok(permit)) => permit,\n                Ok(Err(_)) => {\n                    tracing::error!(\"Semaphore fermé\");\n                    return;\n                }\n                Err(_) => {\n                    tracing::warn!(\"Timeout d'acquisition du sémaphore\");\n                    return;\n                }\n            };\n\n            if let Err(e) = bulk_insert(&pool, &data).await {\n                tracing::error!(error = %e, \"Échec de l'écriture en lot\");\n            }\n\n            drop(permit);\n        });\n    }\n}\n```\n\nCe pattern est idéal pour les écritures de métriques, de logs et d'événements où vous ne pouvez pas vous permettre de bloquer le chemin critique.\n\n## Chasse aux deadlocks\n\nLes deadlocks avec les sémaphores surviennent quand :\n\n1. **Acquisition imbriquée** — Une tâche détient un permit et essaie d'en acquérir un autre du même sémaphore\n2. **Ordre d'acquisition incohérent** — Deux sémaphores acquis dans des ordres différents par différentes tâches\n3. **Fuite de permits** — Des permits qui ne sont jamais libérés (panic sans RAII, ou forget())\n\n### Diagnostic avec tracing\n\n```rust\n#[instrument(skip(semaphore))]\nasync fn process_with_tracing(\n    semaphore: &Semaphore,\n    item: &Item,\n) -> Result\u003C()> {\n    tracing::debug!(\n        available_permits = semaphore.available_permits(),\n        \"En attente de permit\"\n    );\n\n    let _permit = semaphore.acquire().await?;\n\n    tracing::debug!(\n        available_permits = semaphore.available_permits(),\n        \"Permit acquis, traitement en cours\"\n    );\n\n    process(item).await?;\n\n    tracing::debug!(\"Traitement terminé, permit sera libéré\");\n    Ok(())\n}\n```\n\n### Diagnostic avec tokio-console\n\ntokio-console est un débogueur TUI pour les applications tokio :\n\n```bash\n# Installer\ncargo install tokio-console\n\n# Activer dans votre application\n# Cargo.toml: tokio = { features = [\"tracing\"] }\nconsole_subscriber::init();\n\n# Lancer tokio-console dans un autre terminal\ntokio-console\n```\n\ntokio-console affiche :\n- Les tâches en attente et depuis combien de temps\n- Les ressources (sémaphores, mutexes) et qui les détient\n- Les relations d'attente qui révèlent les deadlocks\n\n## Patterns avancés\n\n### Sémaphore pondéré\n\nPour limiter par coût plutôt que par nombre d'opérations :\n\n```rust\nlet semaphore = Arc::new(Semaphore::new(1_000_000)); \u002F\u002F 1MB de budget\n\n\u002F\u002F Chaque opération acquiert des permits proportionnels à sa taille\nlet data_size = data.len();\nlet _permit = semaphore.acquire_many(data_size as u32).await?;\nprocess(data).await;\n```\n\n### try_acquire pour le non-bloquant\n\n```rust\nmatch semaphore.try_acquire() {\n    Ok(permit) => {\n        \u002F\u002F Traiter immédiatement\n        tokio::spawn(async move {\n            process(item).await;\n            drop(permit);\n        });\n    }\n    Err(_) => {\n        \u002F\u002F Capacité pleine — mettre en file d'attente ou rejeter\n        tracing::warn!(\"Contrepression : tâche rejetée\");\n    }\n}\n```\n\n### Sémaphore avec timeout\n\n```rust\nasync fn acquire_with_timeout(\n    semaphore: &Semaphore,\n    timeout_duration: Duration,\n) -> Result\u003CSemaphorePermit\u003C'_>> {\n    match tokio::time::timeout(timeout_duration, semaphore.acquire()).await {\n        Ok(Ok(permit)) => Ok(permit),\n        Ok(Err(_)) => Err(Error::SemaphoreClosed),\n        Err(_) => Err(Error::AcquireTimeout),\n    }\n}\n```\n\n## Concurrence structurée\n\nCombinez les sémaphores avec la concurrence structurée pour un contrôle complet :\n\n```rust\nuse tokio::task::JoinSet;\n\nasync fn process_batch(\n    items: Vec\u003CItem>,\n    max_concurrent: usize,\n) -> Vec\u003CResult\u003COutput>> {\n    let semaphore = Arc::new(Semaphore::new(max_concurrent));\n    let mut join_set = JoinSet::new();\n\n    for item in items {\n        let sem = semaphore.clone();\n        join_set.spawn(async move {\n            let _permit = sem.acquire().await.unwrap();\n            process(item).await\n        });\n    }\n\n    let mut results = Vec::new();\n    while let Some(result) = join_set.join_next().await {\n        results.push(result.unwrap());\n    }\n    results\n}\n```\n\nJoinSet garantit que toutes les tâches sont attendues, et le sémaphore limite la concurrence. Pas de fuite de tâches, pas de fuite de permits.\n\n## Règles de production\n\n1. **Toujours utiliser RAII** — Ne jamais appeler `forget()` sur un permit sauf si vous savez exactement ce que vous faites\n2. **Toujours mettre un timeout** — `acquire()` sans timeout peut bloquer indéfiniment\n3. **Monitorer les permits disponibles** — Exposez `available_permits()` comme métrique Prometheus\n4. **Dimensionner le sémaphore** — Basez-le sur la capacité réelle de la ressource (taille du pool de connexions, limites du fournisseur RPC)\n5. **Tester sous charge** — Les deadlocks ne se manifestent souvent que sous forte concurrence\n\n## Quand utiliser un sémaphore vs d'autres primitives\n\n| Primitive | Cas d'usage |\n|-----------|------------|\n| Semaphore | Limiter N opérations concurrentes |\n| Mutex | Accès exclusif à une ressource partagée |\n| RwLock | Lecture concurrente, écriture exclusive |\n| Channel (bounded) | File d'attente producteur-consommateur |\n| Barrier | Synchronisation de N tâches à un point |\n\n## Conclusion\n\nLe sémaphore tokio est la primitive fondamentale pour le contrôle de contrepression en Rust async. Les permits RAII garantissent l'absence de fuite, les timeouts préviennent les deadlocks, et la concurrence structurée avec JoinSet assure que toutes les tâches sont correctement gérées. Pour les systèmes de production à haut débit, le pattern fire-and-forget avec sémaphore est l'équilibre idéal entre performance et fiabilité.","\u003Ch2 id=\"quand-la-concurrence-rencontre-les-limites-de-ressources\">Quand la concurrence rencontre les limites de ressources\u003C\u002Fh2>\n\u003Cp>Quand vous exécutez un pipeline à haut débit — un bot MEV traitant 180 000 chaînes d’arbitrage par bloc, un serveur API gérant 10 000 requêtes simultanées, ou un job ETL écrivant des millions de lignes — vous finissez inévitablement par atteindre un plafond de ressources. Les pools de connexions à la base de données s’épuisent. Les fournisseurs RPC vous limitent en débit. La mémoire gonfle parce que vous avez créé 50 000 tâches tokio, chacune détenant les données d’une chaîne entière.\u003C\u002Fp>\n\u003Cp>Le sémaphore est la primitive qui résout ce problème : il limite le nombre d’opérations concurrentes à un maximum donné.\u003C\u002Fp>\n\u003Ch2 id=\"tokio-sync-semaphore-les-bases\">tokio::sync::Semaphore : les bases\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::sync::Semaphore;\nuse std::sync::Arc;\n\nlet semaphore = Arc::new(Semaphore::new(10)); \u002F\u002F Maximum 10 opérations concurrentes\n\nfor item in items {\n    let permit = semaphore.clone().acquire_owned().await.unwrap();\n    tokio::spawn(async move {\n        process(item).await;\n        drop(permit); \u002F\u002F Libérer le permit\n    });\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Ccode>acquire_owned()\u003C\u002Fcode> attend qu’un permit soit disponible, puis le retourne. Le permit est un guard RAII — il est automatiquement libéré quand il est droppé. Cela garantit que le sémaphore ne fuit jamais, même en cas de panique.\u003C\u002Fp>\n\u003Ch3>Permits RAII : pourquoi c’est important\u003C\u002Fh3>\n\u003Cp>Le pattern RAII (Resource Acquisition Is Initialization) est crucial pour les sémaphores. Comparez :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">\u002F\u002F DANGEREUX : le permit peut fuir si process() panique\nlet permit = semaphore.acquire().await.unwrap();\nprocess(item).await;\npermit.forget(); \u002F\u002F Oubli intentionnel — rare mais parfois nécessaire\n\n\u002F\u002F SÛR : le permit est libéré même en cas de panique\nlet _permit = semaphore.acquire().await.unwrap();\nprocess(item).await;\n\u002F\u002F _permit droppé automatiquement ici\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"le-pattern-fire-and-forget\">Le pattern fire-and-forget\u003C\u002Fh2>\n\u003Cp>Dans les systèmes de production, vous avez souvent besoin de lancer des opérations d’écriture sans attendre leur achèvement :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct FireAndForgetWriter {\n    semaphore: Arc&lt;Semaphore&gt;,\n    db_pool: PgPool,\n}\n\nimpl FireAndForgetWriter {\n    fn new(max_concurrent: usize, db_pool: PgPool) -&gt; Self {\n        Self {\n            semaphore: Arc::new(Semaphore::new(max_concurrent)),\n            db_pool,\n        }\n    }\n\n    fn write(&amp;self, data: Vec&lt;Record&gt;) {\n        let semaphore = self.semaphore.clone();\n        let pool = self.db_pool.clone();\n\n        tokio::spawn(async move {\n            \u002F\u002F Acquérir avec timeout pour éviter les deadlocks\n            let permit = match tokio::time::timeout(\n                Duration::from_secs(30),\n                semaphore.acquire_owned()\n            ).await {\n                Ok(Ok(permit)) =&gt; permit,\n                Ok(Err(_)) =&gt; {\n                    tracing::error!(\"Semaphore fermé\");\n                    return;\n                }\n                Err(_) =&gt; {\n                    tracing::warn!(\"Timeout d'acquisition du sémaphore\");\n                    return;\n                }\n            };\n\n            if let Err(e) = bulk_insert(&amp;pool, &amp;data).await {\n                tracing::error!(error = %e, \"Échec de l'écriture en lot\");\n            }\n\n            drop(permit);\n        });\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Ce pattern est idéal pour les écritures de métriques, de logs et d’événements où vous ne pouvez pas vous permettre de bloquer le chemin critique.\u003C\u002Fp>\n\u003Ch2 id=\"chasse-aux-deadlocks\">Chasse aux deadlocks\u003C\u002Fh2>\n\u003Cp>Les deadlocks avec les sémaphores surviennent quand :\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Acquisition imbriquée\u003C\u002Fstrong> — Une tâche détient un permit et essaie d’en acquérir un autre du même sémaphore\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Ordre d’acquisition incohérent\u003C\u002Fstrong> — Deux sémaphores acquis dans des ordres différents par différentes tâches\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Fuite de permits\u003C\u002Fstrong> — Des permits qui ne sont jamais libérés (panic sans RAII, ou forget())\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>Diagnostic avec tracing\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">#[instrument(skip(semaphore))]\nasync fn process_with_tracing(\n    semaphore: &amp;Semaphore,\n    item: &amp;Item,\n) -&gt; Result&lt;()&gt; {\n    tracing::debug!(\n        available_permits = semaphore.available_permits(),\n        \"En attente de permit\"\n    );\n\n    let _permit = semaphore.acquire().await?;\n\n    tracing::debug!(\n        available_permits = semaphore.available_permits(),\n        \"Permit acquis, traitement en cours\"\n    );\n\n    process(item).await?;\n\n    tracing::debug!(\"Traitement terminé, permit sera libéré\");\n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Diagnostic avec tokio-console\u003C\u002Fh3>\n\u003Cp>tokio-console est un débogueur TUI pour les applications tokio :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-bash\"># Installer\ncargo install tokio-console\n\n# Activer dans votre application\n# Cargo.toml: tokio = { features = [\"tracing\"] }\nconsole_subscriber::init();\n\n# Lancer tokio-console dans un autre terminal\ntokio-console\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>tokio-console affiche :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Les tâches en attente et depuis combien de temps\u003C\u002Fli>\n\u003Cli>Les ressources (sémaphores, mutexes) et qui les détient\u003C\u002Fli>\n\u003Cli>Les relations d’attente qui révèlent les deadlocks\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"patterns-avanc-s\">Patterns avancés\u003C\u002Fh2>\n\u003Ch3>Sémaphore pondéré\u003C\u002Fh3>\n\u003Cp>Pour limiter par coût plutôt que par nombre d’opérations :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">let semaphore = Arc::new(Semaphore::new(1_000_000)); \u002F\u002F 1MB de budget\n\n\u002F\u002F Chaque opération acquiert des permits proportionnels à sa taille\nlet data_size = data.len();\nlet _permit = semaphore.acquire_many(data_size as u32).await?;\nprocess(data).await;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>try_acquire pour le non-bloquant\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">match semaphore.try_acquire() {\n    Ok(permit) =&gt; {\n        \u002F\u002F Traiter immédiatement\n        tokio::spawn(async move {\n            process(item).await;\n            drop(permit);\n        });\n    }\n    Err(_) =&gt; {\n        \u002F\u002F Capacité pleine — mettre en file d'attente ou rejeter\n        tracing::warn!(\"Contrepression : tâche rejetée\");\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Sémaphore avec timeout\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn acquire_with_timeout(\n    semaphore: &amp;Semaphore,\n    timeout_duration: Duration,\n) -&gt; Result&lt;SemaphorePermit&lt;'_&gt;&gt; {\n    match tokio::time::timeout(timeout_duration, semaphore.acquire()).await {\n        Ok(Ok(permit)) =&gt; Ok(permit),\n        Ok(Err(_)) =&gt; Err(Error::SemaphoreClosed),\n        Err(_) =&gt; Err(Error::AcquireTimeout),\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"concurrence-structur-e\">Concurrence structurée\u003C\u002Fh2>\n\u003Cp>Combinez les sémaphores avec la concurrence structurée pour un contrôle complet :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio::task::JoinSet;\n\nasync fn process_batch(\n    items: Vec&lt;Item&gt;,\n    max_concurrent: usize,\n) -&gt; Vec&lt;Result&lt;Output&gt;&gt; {\n    let semaphore = Arc::new(Semaphore::new(max_concurrent));\n    let mut join_set = JoinSet::new();\n\n    for item in items {\n        let sem = semaphore.clone();\n        join_set.spawn(async move {\n            let _permit = sem.acquire().await.unwrap();\n            process(item).await\n        });\n    }\n\n    let mut results = Vec::new();\n    while let Some(result) = join_set.join_next().await {\n        results.push(result.unwrap());\n    }\n    results\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>JoinSet garantit que toutes les tâches sont attendues, et le sémaphore limite la concurrence. Pas de fuite de tâches, pas de fuite de permits.\u003C\u002Fp>\n\u003Ch2 id=\"r-gles-de-production\">Règles de production\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\u003Cstrong>Toujours utiliser RAII\u003C\u002Fstrong> — Ne jamais appeler \u003Ccode>forget()\u003C\u002Fcode> sur un permit sauf si vous savez exactement ce que vous faites\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Toujours mettre un timeout\u003C\u002Fstrong> — \u003Ccode>acquire()\u003C\u002Fcode> sans timeout peut bloquer indéfiniment\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Monitorer les permits disponibles\u003C\u002Fstrong> — Exposez \u003Ccode>available_permits()\u003C\u002Fcode> comme métrique Prometheus\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Dimensionner le sémaphore\u003C\u002Fstrong> — Basez-le sur la capacité réelle de la ressource (taille du pool de connexions, limites du fournisseur RPC)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Tester sous charge\u003C\u002Fstrong> — Les deadlocks ne se manifestent souvent que sous forte concurrence\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"quand-utiliser-un-s-maphore-vs-d-autres-primitives\">Quand utiliser un sémaphore vs d’autres primitives\u003C\u002Fh2>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Primitive\u003C\u002Fth>\u003Cth>Cas d’usage\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>Semaphore\u003C\u002Ftd>\u003Ctd>Limiter N opérations concurrentes\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Mutex\u003C\u002Ftd>\u003Ctd>Accès exclusif à une ressource partagée\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>RwLock\u003C\u002Ftd>\u003Ctd>Lecture concurrente, écriture exclusive\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Channel (bounded)\u003C\u002Ftd>\u003Ctd>File d’attente producteur-consommateur\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Barrier\u003C\u002Ftd>\u003Ctd>Synchronisation de N tâches à un point\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Ch2 id=\"conclusion\">Conclusion\u003C\u002Fh2>\n\u003Cp>Le sémaphore tokio est la primitive fondamentale pour le contrôle de contrepression en Rust async. Les permits RAII garantissent l’absence de fuite, les timeouts préviennent les deadlocks, et la concurrence structurée avec JoinSet assure que toutes les tâches sont correctement gérées. Pour les systèmes de production à haut débit, le pattern fire-and-forget avec sémaphore est l’équilibre idéal entre performance et fiabilité.\u003C\u002Fp>\n","fr","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:29.525773Z","Sémaphores en Rust async — Chasse aux deadlocks et patterns fire-and-forget","Plongée dans tokio::sync::Semaphore : contrepression, écritures fire-and-forget, diagnostic de deadlocks, solutions de production avec permits RAII et concurrence structurée.","rust sémaphore async",null,"index, follow",[22,27],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","Ingénierie",[33,39,45],{"id":34,"title":35,"slug":36,"excerpt":37,"locale":12,"category_name":31,"published_at":38},"d0000000-0000-0000-0000-000000000677","Pourquoi Bali devient le hub impact-tech d'Asie du Sud-Est en 2026","pourquoi-bali-devient-hub-impact-tech-asie-sud-est-2026","Bali se classe 16e parmi les écosystèmes startups d'Asie du Sud-Est. Avec une concentration croissante de bâtisseurs Web3, de startups IA durables et d'entreprises eco-travel tech, l'île se forge une identité de capitale impact-tech de la région.","2026-03-28T10:44:49.517126Z",{"id":40,"title":41,"slug":42,"excerpt":43,"locale":12,"category_name":31,"published_at":44},"d0000000-0000-0000-0000-000000000676","Le patchwork de la protection des données ASEAN : checklist de conformité pour les développeurs","patchwork-protection-donnees-asean-checklist-conformite-developpeurs","Sept pays de l'ASEAN disposent désormais de lois complètes sur la protection des données, chacune avec des modèles de consentement, des exigences de localisation et des structures de sanctions différents. Voici une checklist pratique de conformité pour les développeurs.","2026-03-28T10:44:49.504560Z",{"id":46,"title":47,"slug":48,"excerpt":49,"locale":12,"category_name":31,"published_at":50},"d0000000-0000-0000-0000-000000000675","La transformation numérique de 29 milliards de dollars d'Indonesia : opportunités pour les éditeurs de logiciels","transformation-numerique-29-milliards-dollars-indonesia-opportunites-editeurs-logiciels","Le marché des services informatiques d'Indonesia devrait atteindre 29,03 milliards de dollars en 2026, contre 24,37 milliards en 2025. L'infrastructure cloud, l'IA, le e-commerce et les centres de données tirent la croissance la plus rapide d'Asie du Sud-Est.","2026-03-28T10:44:49.469231Z",{"id":13,"name":52,"slug":53,"bio":54,"photo_url":19,"linkedin":19,"role":55,"created_at":56,"updated_at":56},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]