Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Softshake 2013 Apiness SA l'envers du décor

Pour une fois, il s’agit de parler de ce que l’on ne montre pas toujours au public : «l’envers du décor», soit le backend base de données et la synchronisation avec l’application iPhone.

Le but de la session est de présenter notre expérience dans le contexte suivant :

Base de données locale Sqlite
Base de données serveur MySql
Synchronisation des données application - serveur


Présentation de l’application
Présentation de l’administration des données (écrans de maintenance des données intégrés à Joomla)
Présentation de l’approche et du code, productivité de développement pour le backend web
Sqlite sans CoreData
Echange des données en JSON
Procédures stockées et vues MySql
Discussion concernant les techniques possibles et la productivité

  • Login to see the comments

  • Be the first to like this

Softshake 2013 Apiness SA l'envers du décor

  1. 1. L’envers du décor 24 Octobre 2013
  2. 2. Apiness SA Laurent Kohler & Nicolas Marfurt www.apiness-software.ch
  3. 3. Le décor
  4. 4. Echange de données Services Web
  5. 5. Echange de données Services Web
  6. 6. Back-end Web Gestion du contenu de l’application (data model «business») Multilingue Multi-utilisateurs Gestion des autorisations - par fonction - sur la donnée • • • Web services de synchronisation
  7. 7. Application mobile Outil d’affichage de données Fonctionne également hors ligne Structure et design simple Synchronisation des données - robuste - performante
  8. 8. Effort de développement
  9. 9. Effort de développement
  10. 10. Effort de développement
  11. 11. Situation «typique» Un client demande «une petite application mobile multi-plateformes» ➡ aspect visuel et fonctionnel ➡ mais aussi développement important : - back-end Web - synchronisation
  12. 12. L’envers du décor
  13. 13. Options Web server & Web services Persistance données côté client mobile - Modèle objet (Core Data) - Modèle relationnel (SQLite natif) - Autre (XML, JSON, plist...)
  14. 14. Contexte: back-end Projet d’exemple Audio Guide LAMP CMS Joomla Données «structurées», modèle relationnel (MySQL)
  15. 15. Contexte: application iOS Projet d’exemple Audio Guide Modèle relationnel Correspond au type de modèle du back-end SQLite (sans Core Data) Optimisation du SQL et des paramètres db
  16. 16. Modèle de données Modèle «server» != modèle «app» Exemples : • Serveur - • Données multilingue Gestion des autorisations Application - Une langue sélectionnée
  17. 17. Au travail !
  18. 18. iOS - Cocoa
  19. 19. Options d’accès à SQLite
  20. 20. Options d’accès à SQLite • Utilisation de Core Data
  21. 21. Options d’accès à SQLite • Utilisation de Core Data
  22. 22. Options d’accès à SQLite • Utilisation de Core Data • Utilisation directe de la libraire SQLite native ➡ Fonctions C
  23. 23. Options d’accès à SQLite • Utilisation de Core Data • Utilisation directe de la libraire SQLite native ➡ • Fonctions C Wrapper ou framework existant ➡ FMDB
  24. 24. Options d’accès à SQLite • Utilisation de Core Data • Utilisation directe de la libraire SQLite native ➡ • Wrapper ou framework existant ➡ • Fonctions C FMDB Wrapper «maison»
  25. 25. Options d’accès à SQLite • Utilisation de Core Data • Utilisation directe de la libraire SQLite native ➡ • Wrapper ou framework existant ➡ • Fonctions C FMDB Wrapper «maison» - raison historique, plaisir d’explorer mainmise sur le code (tests)
  26. 26. Architecture APSSQLite APSSQLiteObjectCoding protocol APSSQLiteDatabaseController APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteObjectPersisting protocol APSSQLiteResultSet
  27. 27. Architecture APSSQLite APSSQLiteObjectCoding protocol APSSQLiteDatabaseController APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteObjectPersisting protocol APSSQLiteResultSet
  28. 28. Architecture MyProjectDatabaseController APSSQLite APSSQLiteObjectCoding protocol APSSQLiteDatabaseController APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteObjectPersisting protocol APSSQLiteResultSet
  29. 29. Architecture MyProjectDatabaseController APSSQLite APSSQLiteObjectCoding protocol APSSQLiteDatabaseController APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteObjectPersisting protocol APSSQLiteResultSet
  30. 30. Architecture MyProjectDatabaseController MyClassResultSet APSSQLiteDatabaseController APSSQLiteObjectCoding protocol APSSQLite APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteObjectPersisting protocol APSSQLiteResultSet
  31. 31. Architecture MyProjectDatabaseController MyClassResultSet APSSQLiteDatabaseController APSSQLiteObjectCoding protocol APSSQLite APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteObjectPersisting protocol APSSQLiteResultSet
  32. 32. Architecture MyProjectDatabaseController MyClassResultSet MyClass APSSQLiteDatabaseController APSSQLiteObjectCoding protocol APSSQLiteObjectPersisting protocol APSSQLite APSSQLiteDatabase APSSQLiteRequest SQLite APSSQLiteResultSet
  33. 33. Intégration Modèle MyClassResultSet MyViewController MyClass MyProjectDatabaseController Contrôleur APSSQLite
  34. 34. Demo
  35. 35. Appel SQLite APSSQLiteDatabase - (BOOL)executeCUDRequest:(APSSQLiteRequest *)request error:(NSError *__autoreleasing *)error { ! BOOL success = YES; ! sqlite3_stmt *statement; int rc = sqlite3_prepare(self.db, [request.query UTF8String], -1, &statement, NULL); ! if(rc == SQLITE_OK) { rc = sqlite3_step(statement); ! ! ! ! ! ! ! ! ! } else { *error = [[self class] errorWithCode:rc message:[NSString stringWithUTF8String:sqlite3_errmsg(self.db)]]; ! success = NO; } ! ! ! } if(rc != SQLITE_DONE) { *error = [[self class] errorWithCode:rc message:[NSString stringWithUTF8String:sqlite3_errmsg(self.db)]]; ! success = NO; } sqlite3_finalize(statement); return success;
  36. 36. Appel SQLite APSSQLiteDatabase - (BOOL)executeCUDRequest:(APSSQLiteRequest *)request error:(NSError *__autoreleasing *)error { ! BOOL success = YES; ! sqlite3_stmt *statement; int rc = sqlite3_prepare(self.db, [request.query UTF8String], -1, &statement, NULL); ! if(rc == SQLITE_OK) { rc = sqlite3_step(statement); ! ! ! ! ! ! ! ! ! } else { *error = [[self class] errorWithCode:rc message:[NSString stringWithUTF8String:sqlite3_errmsg(self.db)]]; ! success = NO; } ! ! ! } if(rc != SQLITE_DONE) { *error = [[self class] errorWithCode:rc message:[NSString stringWithUTF8String:sqlite3_errmsg(self.db)]]; ! success = NO; } sqlite3_finalize(statement); return success;
  37. 37. Exécution d’une requête TopicResultSet : APSSQLiteResultSet + (TopicResultSet *)fetchAll { __block TopicResultSet *resultSet = nil; [[GuideDatabaseController sharedDatabaseController] performBlockAndWait:^(APSSQLiteDatabase *database) { NSString *query = @"SELECT * FROM audio_topic t WHERE t.isPublished = 1 ORDER BY t.sequence"; APSSQLiteRequest *request = [APSSQLiteRequest requestWithQuery:query]; request.resultClass = [TopicResultSet class]; NSError *error = nil; resultSet = [database executeFetchRequest:request error:&error]; // Handle error... }]; return resultSet; }
  38. 38. Exécution d’une requête TopicResultSet : APSSQLiteResultSet + (TopicResultSet *)fetchAll { __block TopicResultSet *resultSet = nil; [[GuideDatabaseController sharedDatabaseController] performBlockAndWait:^(APSSQLiteDatabase *database) { NSString *query = @"SELECT * FROM audio_topic t WHERE t.isPublished = 1 ORDER BY t.sequence"; APSSQLiteRequest *request = [APSSQLiteRequest requestWithQuery:query]; request.resultClass = [TopicResultSet class]; }]; } NSError *error = nil; resultSet = [database executeFetchRequest:request error:&error]; // Handle error... return resultSet;
  39. 39. Affichage des données MyViewController : UITableViewController @interface TopicListViewController () @property (strong, nonatomic) TopicResultSet *topicResultSet; @end - (void)viewDidLoad { [super viewDidLoad]; } self.topicResultSet = [TopicResultSet fetchAll]; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.topicResultSet count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TopicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TopicListViewControllerTopicCell]; NSDictionary *topic = self.topicResultSet[indexPath.row]; cell.textLabel.text = topic[TopicColumnTitle]; cell.detailTextLabel.text = topic[TopicColumnDescription]; } return cell;
  40. 40. Affichage des données MyViewController : UITableViewController @interface TopicListViewController () @property (strong, nonatomic) TopicResultSet *topicResultSet; @end - (void)viewDidLoad { [super viewDidLoad]; } self.topicResultSet = [TopicResultSet fetchAll]; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.topicResultSet count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TopicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TopicListViewControllerTopicCell]; } return cell;
  41. 41. Affichage des données MyViewController : UITableViewController @interface TopicListViewController () @property (strong, nonatomic) TopicResultSet *topicResultSet; @end - (void)viewDidLoad { [super viewDidLoad]; } self.topicResultSet = [TopicResultSet fetchAll]; - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [self.topicResultSet count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TopicTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:TopicListViewControllerTopicCell]; Topic *topic = [self.topicResultSet topicForRowAtIndex:indexPath.row]; cell.textLabel.text = topic.title; cell.detailTextLabel.text = topic.topicDescription; } return cell;
  42. 42. Base de données
  43. 43. Scénarios d’évolution des données • Mises à jour fréquentes mais en petit volume (news) ➡ Intégralité des données pas nécessaire • • Mises à jour en masse (catalogue produit) Installation en masse, mises à jour de petit volume (audio guide)
  44. 44. Contexte Projet d’exemple Audio Guide • Installation de contenu type «audio guide musée» - Texte, images, audio - Création par volume de 100 à 1000 éléments environ - Mise à jour sporadique et en petite quantité
  45. 45. Approches • • • • Option 1 : «naïve» (la plus simple) Option 2 : «typique SQLite» (transaction) Option 3 : «insertion en masse» Option 4 : «insertion en masse» + transaction
  46. 46. Contexte 2 tables relativement petites - table parent : 12 colonnes (int, float, varchar) - table détail : 7 colonnes (int, varchar), foreign key parent Mesure - nombre d’insertions par seconde - données de référence : 1000 parents et 2000 détails
  47. 47. Option 1 Insertion ligne par ligne FOR EACH ROW EXEC:INSERT INTO TBL (c1,c2...) SELECT 123, ‘abc’,...
  48. 48. Option 1 Insertion ligne par ligne insertions / sec FOR EACH ROW EXEC:INSERT INTO TBL (c1,c2...) SELECT 123, ‘abc’,...
  49. 49. Option 1 Insertion ligne par ligne insertions / sec FOR EACH ROW EXEC:INSERT INTO TBL (c1,c2...) SELECT 123, ‘abc’,... Trop lent !
  50. 50. Option 2 Insertion ligne par ligne… dans une transaction BEGIN TRANSACTION FOR EACH ROW EXEC:INSERT INTO TBL (c1,c2...) SELECT 123, ‘abc’,... COMMIT (or rollback)
  51. 51. Option 2 Insertion ligne par ligne… dans une transaction BEGIN TRANSACTION insertions / sec FOR EACH ROW EXEC:INSERT INTO TBL (c1,c2...) SELECT 123, ‘abc’,... COMMIT (or rollback)
  52. 52. Option 2 Insertion ligne par ligne… dans une transaction BEGIN TRANSACTION insertions / sec FOR EACH ROW EXEC:INSERT INTO TBL (c1,c2...) SELECT 123, ‘abc’,... COMMIT (or rollback) 2.5x plus rapide !
  53. 53. Option 3 Insertion de plusieurs lignes en une commande insert EXEC: INSERT VALUES (123, ,(345, ,(678, INTO TBL (c1,c2) ‘Sqlite’) ‘Soft’) ‘Shake’);
  54. 54. Option 3 Insertion de plusieurs lignes en une commande insert SQLite ne connait pas insert many values ! (dépend des versions de SQLite…) EXEC: INSERT VALUES (123, ,(345, ,(678, INTO TBL (c1,c2) ‘Sqlite’) ‘Soft’) ‘Shake’);
  55. 55. Option 3 - syntaxe SQLite Insertion de plusieurs lignes en une commande insert EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’;
  56. 56. Option 3 - syntaxe SQLite Insertion de plusieurs lignes en une commande insert Limite SQLite : au maximum 500 union select ! EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’;
  57. 57. Option 3 - syntaxe SQLite Insertion par paquets de 500 lignes FOR EACH ROW BUILD INSERT STATEMENT WHEN «ENOUGH» EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’;
  58. 58. Option 3 - syntaxe SQLite Insertion par paquets de 500 lignes Code un peu plus complexe FOR EACH ROW BUILD INSERT STATEMENT WHEN «ENOUGH» EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’;
  59. 59. Option 4 Insertion par paquets de 500 lignes… dans une transaction BEGIN TRANSACTION FOR EACH ROW BUILD INSERT STATEMENT WHEN «ENOUGH» EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’; COMMIT (or rollback)
  60. 60. Option 4 Insertion par paquets de 500 lignes… dans une transaction Insertion «rapide» de nombreuses lignes BEGIN TRANSACTION FOR EACH ROW BUILD INSERT STATEMENT WHEN «ENOUGH» EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’; COMMIT (or rollback)
  61. 61. Comparaison des 4 options insertions / sec
  62. 62. Comparaison des 4 options insertions / sec Option 4 45x plus rapide que l’option 1 1500 insertions / seconde sur iPad 3 600 insertions / seconde sur iPhone 4
  63. 63. Option 4 Insertion par paquets de 500 lignes… dans une transaction Performance satisfaisante dans le contexte Au prix d’un code un peu plus complexe Optio adopt n ée ! BEGIN TRANSACTION FOR EACH ROW BUILD INSERT STATEMENT WHEN «ENOUGH» EXEC: INSERT INTO TBL (c1,c2) SELECT 123, ‘Sqlite’ UNION SELECT 345, ‘Soft’ UNION SELECT 678, ‘Shake’; COMMIT (or rollback)
  64. 64. Insert et Update • • Coder «if exists update else insert» • • Long à développer, long à exécuter Update forcément itératif avec SQLite (1 by 1) Approche SQLite efficace : INSERT OR REPLACE INTO... ➡ Si validation intégrité référentielle (foreign key) Différer le contrôle au moment du commit : PRAGMA defer_foreign_keys = ON Optio adopt n ée !
  65. 65. Insert et Replace • • Parfait pour des données provenant à 100% du serveur Attention si certaines colonnes sont maintenues localement ! • Le «insert or replace» modifie toutes les colonnes ➡ Option possible : Utiliser un sub-select pour obtenir la valeur locale
  66. 66. Insert et Replace - exemple EXEC: INSERT INTO news (newsId, newsTitle,newsDesc, hasBeenRead) SELECT 123 ,‘SoftShake 2013’ ,’24 et 25 octobre 2013’ ,(SELECT hasBeenRead FROM news WHERE newsId = 123) UNION SELECT ....
  67. 67. Insert et Replace - exemple EXEC: INSERT INTO news (newsId, newsTitle,newsDesc, hasBeenRead) SELECT 123 ,‘SoftShake 2013’ ,’24 et 25 octobre 2013’ ,(SELECT hasBeenRead FROM news WHERE newsId = 123) UNION SELECT .... work aroun d 30% p lus le ! nt.
  68. 68. Insert et Replace - commentaires • • Lorsque certaines colonnes sont maintenues localement Data model : - une table pour les données «serveur» - une table pour les données «locale» - une view pour accéder aux 2 facilement • • Pas de perte de performance Approche robuste Optio adopt n ée !
  69. 69. Delete • Côté serveur - «soft delete» (flag data inactive) • Côté application - delete physique (à la fin de la synchronisation)
  70. 70. Réorganisation DB • • SQLite est comparable aux autres base de données Après de nombreux insert/update/delete, besoin de : - Récupérer l’espace avec : VACUUM
  71. 71. Quand réorganiser ? • • • A l’ancienne; «une fois par semaine» ? Souvent; après chaque synchro ? Lorsque jugé nécessaire ? - Evaluer les pages vides avec PRAGMA freelist_count - Si «nombreuses» pages inutilisées : effectuer le «vacuum»
  72. 72. Quand réorganiser ? • • • A l’ancienne; «une fois par semaine» ? Souvent; après chaque synchro ? Lorsque jugé nécessaire ? - Evaluer les pages vides avec PRAGMA freelist_count - Si «nombreuses» pages inutilisées : effectuer le «vacuum» vacuum est assez rapide surtout s’il est effectué avant que la db soit totalement désorganisée Important de le faire, «peu importe» quand !
  73. 73. Options SQLite • • Journal mode • Mode «delete» par défaut Attention au mode Write Ahead Log (WAL) • • Meilleure concurrence d’accès Meilleure performance «maintenant»... - Car une partie du travail différée à plus tard ! - Sur device mobile, comportement potentiellement ennuyeux
  74. 74. Pour conclure • • SQLite est excellent et très performant ! • Utiliser EXPLAIN pour analyser ce que fait SQLite En définissant bien les indexes, pas de soucis de performance du côté des select
  75. 75. Merci à vous tous pour votre attention !
  76. 76. No SQL ? Core Data ? SQLite ? Back-end Web? Discussion
  77. 77. BACKUP SLIDES
  78. 78. Backend web - gestion des données
  79. 79. Backend web - gestion des données Outils existants (connus) • Contraintes par rapport au data model • (Très) rapide pour des fonctions simples • Risque d’atteindre une impasse Développement «sur mesure» • trop long, trop coûteux
  80. 80. Gestion des données - nos choix • • «middle-tier» commun qui génère le html en fonction de paramètres et accès aux données par procédures stockées • • • Code PHP stable, on génère/écrit seulement le sql Rapide Optimisation des stored procedures toujours possible Outil «maison» : JEdit • Développement par Apiness (Marc Perroulaz, L.Kohler)
  81. 81. Core Data - iOS 7 - Sqlite • Version SQLite • • • • - iOS 6: version 3.7.13 - iOS 7.0: version 3.7.13 Options Core Data • • Utilisation du mode WAL par défaut depuis iOS 7 Possibilité de passer des options SQLite à Core Data, entre autre le mode de journalisation: • @{ NSSQLitePragmasOption : @{@"journal_mode" : @"DELETE"} } Trace Core Data (iOS 7 / sans analyse) • • • • - pragma journal_mode=wal - pragma cache_size=200 - pragma page_count - pragma freelist_count

×